]> git.draconx.ca Git - dxcommon.git/blob - src/getline.h
dx_getline: Fix EOF handling in standard C fallback.
[dxcommon.git] / src / getline.h
1 /*
2  * Copyright © 2024 Nick Bowler
3  *
4  * getline-like function which removes trailing newline (if any).
5  *
6  * If HAVE_GETLINE is not defined (or defined to 0) then a standard C
7  * implementation is used.  Othewrise, the POSIX getline function
8  * is called.
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
22  */
23 #ifndef DX_GETLINE_H_
24 #define DX_GETLINE_H_
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <limits.h>
30
31 /*
32  * Size of the initial buffer allocated internally by the fallback
33  * implementation when *linebuf is NULL.
34  */
35 #ifndef DX_GETLINE_INITIAL_ALLOC
36 #  define DX_GETLINE_INITIAL_ALLOC 75
37 #endif
38
39 enum {
40         DX_GETLINE_OK     =  1,
41         DX_GETLINE_EOF    =  0,
42         DX_GETLINE_ERROR  = -1,
43         DX_GETLINE_ENOMEM = -2
44 };
45
46 /*
47  * Wrapper around getline with standard C fallback.
48  *
49  * Note that for portability to some getline implementations (e.g., FreeBSD)
50  * both *linebuf and *n should be set to zero on the initial call.
51  *
52  * If pre-allocating a buffer, ensure that its size is more than 1 byte,
53  * otherwise AIX 7.2 getline fails to work correctly.
54  *
55  * Returns 1 (DX_GETLINE_OK) if a line was read or 0 (DX_GETLINE_EOF) if
56  * no line could be read because the end of file was reached.
57  *
58  * On failure, returns a negative value.  If the C library input call failed
59  * then the return value is DX_GETLINE_ERROR and the reason for the failure
60  * should be available in errno.
61  *
62  * For the standard C fallback only, a return value of DX_GETLINE_ENOMEM
63  * indicates that the buffer allocation could not be expanded to fit the
64  * input line.
65  */
66 static inline int dx_getline(char **linebuf, size_t *n, FILE *f)
67 {
68 #if HAVE_GETLINE
69         ssize_t rc;
70
71         if ((rc = getline(linebuf, n, f)) < 0) {
72                 if (ferror(f))
73                         return DX_GETLINE_ERROR;
74                 return DX_GETLINE_EOF;
75         }
76
77         if (rc-- && (*linebuf)[rc] == '\n')
78                 (*linebuf)[rc] = '\0';
79
80         return DX_GETLINE_OK;
81 #else
82         char *work = *linebuf;
83         size_t pos = 0;
84         size_t sz;
85
86         if (!work) {
87                 sz = DX_GETLINE_INITIAL_ALLOC;
88                 goto initial_alloc;
89         }
90
91         for (sz = *n;;) {
92                 if (!fgets(&work[pos], sz - pos, f)) {
93                         if (ferror(f))
94                                 return DX_GETLINE_ERROR;
95
96                         return pos ? DX_GETLINE_OK : DX_GETLINE_EOF;
97                 }
98
99                 pos += strlen(&work[pos]);
100                 if (work[pos-1] == '\n') {
101                         work[pos-1] = '\0';
102                         return DX_GETLINE_OK;
103                 }
104
105                 if (sz > INT_MAX/2 || sz > ((size_t)-1)/4)
106                         break;
107
108                 sz = ((sz*4) + 2) / 3;
109 initial_alloc:
110                 work = realloc(work, sz);
111                 if (!work)
112                         break;
113                 *linebuf = work;
114                 *n = sz;
115         }
116
117         return DX_GETLINE_ENOMEM;
118 #endif
119 }
120
121 #endif