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