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