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