Line data Source code
1 : /*------------------------------------------------------------------------- 2 : * 3 : * pg_get_line.c 4 : * fgets() with an expansible result buffer 5 : * 6 : * Portions Copyright (c) 1996-2024, 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 1838 : pg_get_line(FILE *stream, PromptInterruptContext *prompt_ctx) 60 : { 61 : StringInfoData buf; 62 : 63 1838 : initStringInfo(&buf); 64 : 65 1838 : if (!pg_get_line_append(stream, &buf, prompt_ctx)) 66 : { 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 : } 74 : 75 1838 : return buf.data; 76 : } 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 1899840 : pg_get_line_buf(FILE *stream, StringInfo buf) 96 : { 97 : /* We just need to drop any data from the previous call */ 98 1899840 : resetStringInfo(buf); 99 1899840 : return pg_get_line_append(stream, buf, NULL); 100 : } 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 2231208 : pg_get_line_append(FILE *stream, StringInfo buf, 125 : PromptInterruptContext *prompt_ctx) 126 : { 127 2231208 : int orig_len = buf->len; 128 : 129 2231208 : if (prompt_ctx && sigsetjmp(*((sigjmp_buf *) prompt_ctx->jmpbuf), 1) != 0) 130 : { 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 : } 138 : 139 : /* Loop until newline or EOF/error */ 140 : for (;;) 141 42 : { 142 : char *res; 143 : 144 : /* Enable longjmp while waiting for input */ 145 2231250 : if (prompt_ctx) 146 4 : *(prompt_ctx->enabled) = true; 147 : 148 : /* Read some data, appending it to whatever we already have */ 149 2231250 : res = fgets(buf->data + buf->len, buf->maxlen - buf->len, stream); 150 : 151 : /* Disable longjmp again, then break if fgets failed */ 152 2231250 : if (prompt_ctx) 153 4 : *(prompt_ctx->enabled) = false; 154 : 155 2231250 : if (res == NULL) 156 5502 : break; 157 : 158 : /* Got data, so update buf->len */ 159 2225748 : buf->len += strlen(buf->data + buf->len); 160 : 161 : /* Done if we have collected a newline */ 162 2225748 : if (buf->len > orig_len && buf->data[buf->len - 1] == '\n') 163 2225706 : return true; 164 : 165 : /* Make some more room in the buffer, and loop to read more data */ 166 42 : enlargeStringInfo(buf, 128); 167 : } 168 : 169 : /* Check for I/O errors and EOF */ 170 5502 : if (ferror(stream) || buf->len == orig_len) 171 : { 172 : /* Discard any data we collected before detecting error */ 173 5464 : buf->len = orig_len; 174 5464 : buf->data[orig_len] = '\0'; 175 5464 : return false; 176 : } 177 : 178 : /* No newline at EOF, but we did collect some data */ 179 38 : return true; 180 : }