Line data Source code
1 : /*
2 : * psql - the PostgreSQL interactive terminal
3 : *
4 : * Copyright (c) 2000-2026, PostgreSQL Global Development Group
5 : *
6 : * src/bin/psql/prompt.c
7 : */
8 : #include "postgres_fe.h"
9 :
10 : #ifdef WIN32
11 : #include <io.h>
12 : #include <win32.h>
13 : #endif
14 :
15 : #include "common.h"
16 : #include "common/string.h"
17 : #include "input.h"
18 : #include "libpq/pqcomm.h"
19 : #include "prompt.h"
20 : #include "settings.h"
21 :
22 : /*--------------------------
23 : * get_prompt
24 : *
25 : * Returns a statically allocated prompt made by interpolating certain
26 : * tcsh style escape sequences into pset.vars "PROMPT1|2|3".
27 : * (might not be completely multibyte safe)
28 : *
29 : * Defined interpolations are:
30 : * %M - database server "hostname.domainname", "[local]" for AF_UNIX
31 : * sockets, "[local:/dir/name]" if not default
32 : * %m - like %M, but hostname only (before first dot), or always "[local]"
33 : * %p - backend pid
34 : * %P - pipeline status: on, off or abort
35 : * %> - database server port number
36 : * %n - database user name
37 : * %S - search_path
38 : * %s - service
39 : * %/ - current database
40 : * %~ - like %/ but "~" when database name equals user name
41 : * %w - whitespace of the same width as the most recent output of PROMPT1
42 : * %# - "#" if superuser, ">" otherwise
43 : * %R - in prompt1 normally =, or ^ if single line mode,
44 : * or a ! if session is not connected to a database;
45 : * in prompt2 -, *, ', or ";
46 : * in prompt3 nothing
47 : * %i - "standby" or "primary" depending on the server's in_hot_standby
48 : * status, or "?" if unavailable (empty if unknown)
49 : * %x - transaction status: empty, *, !, ? (unknown or no connection)
50 : * %l - The line number inside the current statement, starting from 1.
51 : * %? - the error code of the last query (not yet implemented)
52 : * %% - a percent sign
53 : *
54 : * %[0-9] - the character with the given decimal code
55 : * %0[0-7] - the character with the given octal code
56 : * %0x[0-9A-Fa-f] - the character with the given hexadecimal code
57 : *
58 : * %`command` - The result of executing command in /bin/sh with trailing
59 : * newline stripped.
60 : * %:name: - The value of the psql variable 'name'
61 : * (those will not be rescanned for more escape sequences!)
62 : *
63 : * %[ ... %] - tell readline that the contained text is invisible
64 : *
65 : * If the application-wide prompts become NULL somehow, the returned string
66 : * will be empty (not NULL!).
67 : *--------------------------
68 : */
69 :
70 : char *
71 134 : get_prompt(promptStatus_t status, ConditionalStack cstack)
72 : {
73 : #define MAX_PROMPT_SIZE 256
74 : static char destination[MAX_PROMPT_SIZE + 1];
75 : char buf[MAX_PROMPT_SIZE + 1];
76 134 : bool esc = false;
77 : const char *p;
78 134 : const char *prompt_string = "? ";
79 : static size_t last_prompt1_width = 0;
80 :
81 134 : switch (status)
82 : {
83 130 : case PROMPT_READY:
84 130 : prompt_string = pset.prompt1;
85 130 : break;
86 :
87 4 : case PROMPT_CONTINUE:
88 : case PROMPT_SINGLEQUOTE:
89 : case PROMPT_DOUBLEQUOTE:
90 : case PROMPT_DOLLARQUOTE:
91 : case PROMPT_COMMENT:
92 : case PROMPT_PAREN:
93 4 : prompt_string = pset.prompt2;
94 4 : break;
95 :
96 0 : case PROMPT_COPY:
97 0 : prompt_string = pset.prompt3;
98 0 : break;
99 : }
100 :
101 134 : destination[0] = '\0';
102 :
103 134 : for (p = prompt_string;
104 1340 : *p && strlen(destination) < sizeof(destination) - 1;
105 1206 : p++)
106 : {
107 1206 : memset(buf, 0, sizeof(buf));
108 1206 : if (esc)
109 : {
110 536 : switch (*p)
111 : {
112 : /* Current database */
113 134 : case '/':
114 134 : if (pset.db)
115 134 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
116 134 : break;
117 0 : case '~':
118 0 : if (pset.db)
119 : {
120 : const char *var;
121 :
122 0 : if (strcmp(PQdb(pset.db), PQuser(pset.db)) == 0 ||
123 0 : ((var = getenv("PGDATABASE")) && strcmp(var, PQdb(pset.db)) == 0))
124 0 : strlcpy(buf, "~", sizeof(buf));
125 : else
126 0 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
127 : }
128 0 : break;
129 :
130 : /* Whitespace of the same width as the last PROMPT1 */
131 0 : case 'w':
132 0 : if (pset.db)
133 0 : memset(buf, ' ',
134 0 : Min(last_prompt1_width, sizeof(buf) - 1));
135 0 : break;
136 :
137 : /* DB server hostname (long/short) */
138 0 : case 'M':
139 : case 'm':
140 0 : if (pset.db)
141 : {
142 0 : const char *host = PQhost(pset.db);
143 :
144 : /* INET socket */
145 0 : if (host && host[0] && !is_unixsock_path(host))
146 : {
147 0 : strlcpy(buf, host, sizeof(buf));
148 0 : if (*p == 'm')
149 0 : buf[strcspn(buf, ".")] = '\0';
150 : }
151 : /* UNIX socket */
152 : else
153 : {
154 0 : if (!host
155 0 : || strcmp(host, DEFAULT_PGSOCKET_DIR) == 0
156 0 : || *p == 'm')
157 0 : strlcpy(buf, "[local]", sizeof(buf));
158 : else
159 0 : snprintf(buf, sizeof(buf), "[local:%s]", host);
160 : }
161 : }
162 0 : break;
163 : /* DB server port number */
164 0 : case '>':
165 0 : if (pset.db && PQport(pset.db))
166 0 : strlcpy(buf, PQport(pset.db), sizeof(buf));
167 0 : break;
168 : /* DB server user name */
169 0 : case 'n':
170 0 : if (pset.db)
171 0 : strlcpy(buf, session_username(), sizeof(buf));
172 0 : break;
173 : /* search_path */
174 0 : case 'S':
175 0 : if (pset.db)
176 : {
177 0 : const char *sp = PQparameterStatus(pset.db, "search_path");
178 :
179 : /* Use ? for versions that don't report search_path. */
180 0 : strlcpy(buf, sp ? sp : "?", sizeof(buf));
181 : }
182 0 : break;
183 : /* service name */
184 0 : case 's':
185 : {
186 0 : const char *service_name = GetVariable(pset.vars, "SERVICE");
187 :
188 0 : if (service_name)
189 0 : strlcpy(buf, service_name, sizeof(buf));
190 : }
191 0 : break;
192 : /* backend pid */
193 0 : case 'p':
194 0 : if (pset.db)
195 : {
196 0 : int pid = PQbackendPID(pset.db);
197 :
198 0 : if (pid)
199 0 : snprintf(buf, sizeof(buf), "%d", pid);
200 : }
201 0 : break;
202 : /* pipeline status */
203 0 : case 'P':
204 0 : if (pset.db)
205 : {
206 0 : PGpipelineStatus status = PQpipelineStatus(pset.db);
207 :
208 0 : if (status == PQ_PIPELINE_ON)
209 0 : strlcpy(buf, "on", sizeof(buf));
210 0 : else if (status == PQ_PIPELINE_ABORTED)
211 0 : strlcpy(buf, "abort", sizeof(buf));
212 : else
213 0 : strlcpy(buf, "off", sizeof(buf));
214 : }
215 0 : break;
216 0 : case '0':
217 : case '1':
218 : case '2':
219 : case '3':
220 : case '4':
221 : case '5':
222 : case '6':
223 : case '7':
224 0 : *buf = (char) strtol(p, unconstify(char **, &p), 8);
225 0 : --p;
226 0 : break;
227 134 : case 'R':
228 : switch (status)
229 : {
230 130 : case PROMPT_READY:
231 130 : if (cstack != NULL && !conditional_active(cstack))
232 0 : buf[0] = '@';
233 130 : else if (!pset.db)
234 0 : buf[0] = '!';
235 130 : else if (!pset.singleline)
236 130 : buf[0] = '=';
237 : else
238 0 : buf[0] = '^';
239 130 : break;
240 2 : case PROMPT_CONTINUE:
241 2 : buf[0] = '-';
242 2 : break;
243 0 : case PROMPT_SINGLEQUOTE:
244 0 : buf[0] = '\'';
245 0 : break;
246 0 : case PROMPT_DOUBLEQUOTE:
247 0 : buf[0] = '"';
248 0 : break;
249 0 : case PROMPT_DOLLARQUOTE:
250 0 : buf[0] = '$';
251 0 : break;
252 0 : case PROMPT_COMMENT:
253 0 : buf[0] = '*';
254 0 : break;
255 2 : case PROMPT_PAREN:
256 2 : buf[0] = '(';
257 2 : break;
258 0 : default:
259 0 : buf[0] = '\0';
260 0 : break;
261 : }
262 134 : break;
263 0 : case 'i':
264 0 : if (pset.db)
265 : {
266 0 : const char *hs = PQparameterStatus(pset.db, "in_hot_standby");
267 :
268 0 : if (hs)
269 : {
270 0 : if (strcmp(hs, "on") == 0)
271 0 : strlcpy(buf, "standby", sizeof(buf));
272 : else
273 0 : strlcpy(buf, "primary", sizeof(buf));
274 : }
275 : /* Use ? for versions that don't report in_hot_standby */
276 : else
277 0 : buf[0] = '?';
278 : }
279 0 : break;
280 134 : case 'x':
281 134 : if (!pset.db)
282 0 : buf[0] = '?';
283 : else
284 134 : switch (PQtransactionStatus(pset.db))
285 : {
286 134 : case PQTRANS_IDLE:
287 134 : buf[0] = '\0';
288 134 : break;
289 0 : case PQTRANS_ACTIVE:
290 : case PQTRANS_INTRANS:
291 0 : buf[0] = '*';
292 0 : break;
293 0 : case PQTRANS_INERROR:
294 0 : buf[0] = '!';
295 0 : break;
296 0 : default:
297 0 : buf[0] = '?';
298 0 : break;
299 : }
300 134 : break;
301 :
302 0 : case 'l':
303 0 : snprintf(buf, sizeof(buf), UINT64_FORMAT, pset.stmt_lineno);
304 0 : break;
305 :
306 0 : case '?':
307 : /* not here yet */
308 0 : break;
309 :
310 134 : case '#':
311 134 : if (is_superuser())
312 134 : buf[0] = '#';
313 : else
314 0 : buf[0] = '>';
315 134 : break;
316 :
317 : /* execute command */
318 0 : case '`':
319 : {
320 0 : int cmdend = strcspn(p + 1, "`");
321 0 : char *file = pnstrdup(p + 1, cmdend);
322 : FILE *fd;
323 :
324 0 : fflush(NULL);
325 0 : fd = popen(file, "r");
326 0 : if (fd)
327 : {
328 0 : if (fgets(buf, sizeof(buf), fd) == NULL)
329 0 : buf[0] = '\0';
330 0 : pclose(fd);
331 : }
332 :
333 : /* strip trailing newline and carriage return */
334 0 : (void) pg_strip_crlf(buf);
335 :
336 0 : free(file);
337 0 : p += cmdend + 1;
338 0 : break;
339 : }
340 :
341 : /* interpolate variable */
342 0 : case ':':
343 : {
344 0 : int nameend = strcspn(p + 1, ":");
345 0 : char *name = pnstrdup(p + 1, nameend);
346 : const char *val;
347 :
348 0 : val = GetVariable(pset.vars, name);
349 0 : if (val)
350 0 : strlcpy(buf, val, sizeof(buf));
351 0 : free(name);
352 0 : p += nameend + 1;
353 0 : break;
354 : }
355 :
356 0 : case '[':
357 : case ']':
358 : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
359 :
360 : /*
361 : * readline >=4.0 undocumented feature: non-printing
362 : * characters in prompt strings must be marked as such, in
363 : * order to properly display the line during editing.
364 : */
365 0 : buf[0] = (*p == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
366 0 : buf[1] = '\0';
367 : #endif /* USE_READLINE */
368 0 : break;
369 :
370 0 : default:
371 0 : buf[0] = *p;
372 0 : buf[1] = '\0';
373 0 : break;
374 : }
375 536 : esc = false;
376 : }
377 670 : else if (*p == '%')
378 536 : esc = true;
379 : else
380 : {
381 134 : buf[0] = *p;
382 134 : buf[1] = '\0';
383 134 : esc = false;
384 : }
385 :
386 1206 : if (!esc)
387 670 : strlcat(destination, buf, sizeof(destination));
388 : }
389 :
390 : /* Compute the visible width of PROMPT1, for PROMPT2's %w */
391 134 : if (prompt_string == pset.prompt1)
392 : {
393 130 : char *p = destination;
394 130 : char *end = p + strlen(p);
395 130 : bool visible = true;
396 :
397 130 : last_prompt1_width = 0;
398 1560 : while (*p)
399 : {
400 : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
401 1430 : if (*p == RL_PROMPT_START_IGNORE)
402 : {
403 0 : visible = false;
404 0 : ++p;
405 : }
406 1430 : else if (*p == RL_PROMPT_END_IGNORE)
407 : {
408 0 : visible = true;
409 0 : ++p;
410 : }
411 : else
412 : #endif
413 : {
414 : int chlen,
415 : chwidth;
416 :
417 1430 : chlen = PQmblen(p, pset.encoding);
418 1430 : if (p + chlen > end)
419 0 : break; /* Invalid string */
420 :
421 1430 : if (visible)
422 : {
423 1430 : chwidth = PQdsplen(p, pset.encoding);
424 :
425 1430 : if (*p == '\n')
426 0 : last_prompt1_width = 0;
427 1430 : else if (chwidth > 0)
428 1430 : last_prompt1_width += chwidth;
429 : }
430 :
431 1430 : p += chlen;
432 : }
433 : }
434 : }
435 :
436 134 : return destination;
437 : }
|