/src/postgres/src/common/pg_get_line.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * pg_get_line.c |
4 | | * fgets() with an expansible result buffer |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/common/pg_get_line.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | #ifndef FRONTEND |
16 | | #include "postgres.h" |
17 | | #else |
18 | | #include "postgres_fe.h" |
19 | | #endif |
20 | | |
21 | | #include <setjmp.h> |
22 | | |
23 | | #include "common/string.h" |
24 | | #include "lib/stringinfo.h" |
25 | | |
26 | | |
27 | | /* |
28 | | * pg_get_line() |
29 | | * |
30 | | * This is meant to be equivalent to fgets(), except that instead of |
31 | | * reading into a caller-supplied, fixed-size buffer, it reads into |
32 | | * a palloc'd (in frontend, really malloc'd) string, which is resized |
33 | | * as needed to handle indefinitely long input lines. The caller is |
34 | | * responsible for pfree'ing the result string when appropriate. |
35 | | * |
36 | | * As with fgets(), returns NULL if there is a read error or if no |
37 | | * characters are available before EOF. The caller can distinguish |
38 | | * these cases by checking ferror(stream). |
39 | | * |
40 | | * Since this is meant to be equivalent to fgets(), the trailing newline |
41 | | * (if any) is not stripped. Callers may wish to apply pg_strip_crlf(). |
42 | | * |
43 | | * Note that while I/O errors are reflected back to the caller to be |
44 | | * dealt with, an OOM condition for the palloc'd buffer will not be; |
45 | | * there'll be an ereport(ERROR) or exit(1) inside stringinfo.c. |
46 | | * |
47 | | * Also note that the palloc'd buffer is usually a lot longer than |
48 | | * strictly necessary, so it may be inadvisable to use this function |
49 | | * to collect lots of long-lived data. A less memory-hungry option |
50 | | * is to use pg_get_line_buf() or pg_get_line_append() in a loop, |
51 | | * then pstrdup() each line. |
52 | | * |
53 | | * prompt_ctx can optionally be provided to allow this function to be |
54 | | * canceled via an existing SIGINT signal handler that will longjmp to the |
55 | | * specified place only when *(prompt_ctx->enabled) is true. If canceled, |
56 | | * this function returns NULL, and prompt_ctx->canceled is set to true. |
57 | | */ |
58 | | char * |
59 | | pg_get_line(FILE *stream, PromptInterruptContext *prompt_ctx) |
60 | 0 | { |
61 | 0 | StringInfoData buf; |
62 | |
|
63 | 0 | initStringInfo(&buf); |
64 | |
|
65 | 0 | if (!pg_get_line_append(stream, &buf, prompt_ctx)) |
66 | 0 | { |
67 | | /* ensure that free() doesn't mess up errno */ |
68 | 0 | int save_errno = errno; |
69 | |
|
70 | 0 | pfree(buf.data); |
71 | 0 | errno = save_errno; |
72 | 0 | return NULL; |
73 | 0 | } |
74 | | |
75 | 0 | return buf.data; |
76 | 0 | } |
77 | | |
78 | | /* |
79 | | * pg_get_line_buf() |
80 | | * |
81 | | * This has similar behavior to pg_get_line(), and thence to fgets(), |
82 | | * except that the collected data is returned in a caller-supplied |
83 | | * StringInfo buffer. This is a convenient API for code that just |
84 | | * wants to read and process one line at a time, without any artificial |
85 | | * limit on line length. |
86 | | * |
87 | | * Returns true if a line was successfully collected (including the |
88 | | * case of a non-newline-terminated line at EOF). Returns false if |
89 | | * there was an I/O error or no data was available before EOF. |
90 | | * (Check ferror(stream) to distinguish these cases.) |
91 | | * |
92 | | * In the false-result case, buf is reset to empty. |
93 | | */ |
94 | | bool |
95 | | pg_get_line_buf(FILE *stream, StringInfo buf) |
96 | 0 | { |
97 | | /* We just need to drop any data from the previous call */ |
98 | 0 | resetStringInfo(buf); |
99 | 0 | return pg_get_line_append(stream, buf, NULL); |
100 | 0 | } |
101 | | |
102 | | /* |
103 | | * pg_get_line_append() |
104 | | * |
105 | | * This has similar behavior to pg_get_line(), and thence to fgets(), |
106 | | * except that the collected data is appended to whatever is in *buf. |
107 | | * This is useful in preference to pg_get_line_buf() if the caller wants |
108 | | * to merge some lines together, e.g. to implement backslash continuation. |
109 | | * |
110 | | * Returns true if a line was successfully collected (including the |
111 | | * case of a non-newline-terminated line at EOF). Returns false if |
112 | | * there was an I/O error or no data was available before EOF. |
113 | | * (Check ferror(stream) to distinguish these cases.) |
114 | | * |
115 | | * In the false-result case, the contents of *buf are logically unmodified, |
116 | | * though it's possible that the buffer has been resized. |
117 | | * |
118 | | * prompt_ctx can optionally be provided to allow this function to be |
119 | | * canceled via an existing SIGINT signal handler that will longjmp to the |
120 | | * specified place only when *(prompt_ctx->enabled) is true. If canceled, |
121 | | * this function returns false, and prompt_ctx->canceled is set to true. |
122 | | */ |
123 | | bool |
124 | | pg_get_line_append(FILE *stream, StringInfo buf, |
125 | | PromptInterruptContext *prompt_ctx) |
126 | 0 | { |
127 | 0 | int orig_len = buf->len; |
128 | |
|
129 | 0 | if (prompt_ctx && sigsetjmp(*((sigjmp_buf *) prompt_ctx->jmpbuf), 1) != 0) |
130 | 0 | { |
131 | | /* Got here with longjmp */ |
132 | 0 | prompt_ctx->canceled = true; |
133 | | /* Discard any data we collected before detecting error */ |
134 | 0 | buf->len = orig_len; |
135 | 0 | buf->data[orig_len] = '\0'; |
136 | 0 | return false; |
137 | 0 | } |
138 | | |
139 | | /* Loop until newline or EOF/error */ |
140 | 0 | for (;;) |
141 | 0 | { |
142 | 0 | char *res; |
143 | | |
144 | | /* Enable longjmp while waiting for input */ |
145 | 0 | if (prompt_ctx) |
146 | 0 | *(prompt_ctx->enabled) = true; |
147 | | |
148 | | /* Read some data, appending it to whatever we already have */ |
149 | 0 | res = fgets(buf->data + buf->len, buf->maxlen - buf->len, stream); |
150 | | |
151 | | /* Disable longjmp again, then break if fgets failed */ |
152 | 0 | if (prompt_ctx) |
153 | 0 | *(prompt_ctx->enabled) = false; |
154 | |
|
155 | 0 | if (res == NULL) |
156 | 0 | break; |
157 | | |
158 | | /* Got data, so update buf->len */ |
159 | 0 | buf->len += strlen(buf->data + buf->len); |
160 | | |
161 | | /* Done if we have collected a newline */ |
162 | 0 | if (buf->len > orig_len && buf->data[buf->len - 1] == '\n') |
163 | 0 | return true; |
164 | | |
165 | | /* Make some more room in the buffer, and loop to read more data */ |
166 | 0 | enlargeStringInfo(buf, 128); |
167 | 0 | } |
168 | | |
169 | | /* Check for I/O errors and EOF */ |
170 | 0 | if (ferror(stream) || buf->len == orig_len) |
171 | 0 | { |
172 | | /* Discard any data we collected before detecting error */ |
173 | 0 | buf->len = orig_len; |
174 | 0 | buf->data[orig_len] = '\0'; |
175 | 0 | return false; |
176 | 0 | } |
177 | | |
178 | | /* No newline at EOF, but we did collect some data */ |
179 | 0 | return true; |
180 | 0 | } |