YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/postgres/src/bin/psql/input.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * psql - the PostgreSQL interactive terminal
3
 *
4
 * Copyright (c) 2000-2018, PostgreSQL Global Development Group
5
 *
6
 * src/bin/psql/input.c
7
 */
8
#include "postgres_fe.h"
9
10
#ifndef WIN32
11
#include <unistd.h>
12
#endif
13
#include <fcntl.h>
14
#include <limits.h>
15
16
#include "input.h"
17
#include "settings.h"
18
#include "tab-complete.h"
19
#include "common.h"
20
21
#ifndef WIN32
22
0
#define PSQLHISTORY ".psql_history"
23
#else
24
#define PSQLHISTORY "psql_history"
25
#endif
26
27
/* Runtime options for turning off readline and history */
28
/* (of course there is no runtime command for doing that :) */
29
#ifdef USE_READLINE
30
static bool useReadline;
31
static bool useHistory;
32
33
static char *psql_history;
34
35
static int  history_lines_added;
36
37
38
/*
39
 *  Preserve newlines in saved queries by mapping '\n' to NL_IN_HISTORY
40
 *
41
 *  It is assumed NL_IN_HISTORY will never be entered by the user
42
 *  nor appear inside a multi-byte string.  0x00 is not properly
43
 *  handled by the readline routines so it can not be used
44
 *  for this purpose.
45
 */
46
0
#define NL_IN_HISTORY 0x01
47
#endif
48
49
static void finishInput(void);
50
51
52
/*
53
 * gets_interactive()
54
 *
55
 * Gets a line of interactive input, using readline if desired.
56
 *
57
 * prompt: the prompt string to be used
58
 * query_buf: buffer containing lines already read in the current command
59
 * (query_buf is not modified here, but may be consulted for tab completion)
60
 *
61
 * The result is a malloc'd string.
62
 *
63
 * Caller *must* have set up sigint_interrupt_jmp before calling.
64
 */
65
char *
66
gets_interactive(const char *prompt, PQExpBuffer query_buf)
67
0
{
68
0
#ifdef USE_READLINE
69
0
  if (useReadline)
70
0
  {
71
0
    char     *result;
72
73
    /*
74
     * Some versions of readline don't notice SIGWINCH signals that arrive
75
     * when not actively reading input.  The simplest fix is to always
76
     * re-read the terminal size.  This leaves a window for SIGWINCH to be
77
     * missed between here and where readline() enables libreadline's
78
     * signal handler, but that's probably short enough to be ignored.
79
     */
80
#ifdef HAVE_RL_RESET_SCREEN_SIZE
81
    rl_reset_screen_size();
82
#endif
83
84
    /* Make current query_buf available to tab completion callback */
85
0
    tab_completion_query_buf = query_buf;
86
87
    /* Enable SIGINT to longjmp to sigint_interrupt_jmp */
88
0
    sigint_interrupt_enabled = true;
89
90
    /* On some platforms, readline is declared as readline(char *) */
91
0
    result = readline((char *) prompt);
92
93
    /* Disable SIGINT again */
94
0
    sigint_interrupt_enabled = false;
95
96
    /* Pure neatnik-ism */
97
0
    tab_completion_query_buf = NULL;
98
99
0
    return result;
100
0
  }
101
0
#endif
102
103
0
  fputs(prompt, stdout);
104
0
  fflush(stdout);
105
0
  return gets_fromFile(stdin);
106
0
}
107
108
109
/*
110
 * Append the line to the history buffer, making sure there is a trailing '\n'
111
 */
112
void
113
pg_append_history(const char *s, PQExpBuffer history_buf)
114
0
{
115
0
#ifdef USE_READLINE
116
0
  if (useHistory && s)
117
0
  {
118
0
    appendPQExpBufferStr(history_buf, s);
119
0
    if (!s[0] || s[strlen(s) - 1] != '\n')
120
0
      appendPQExpBufferChar(history_buf, '\n');
121
0
  }
122
0
#endif
123
0
}
124
125
126
/*
127
 * Emit accumulated history entry to readline's history mechanism,
128
 * then reset the buffer to empty.
129
 *
130
 * Note: we write nothing if history_buf is empty, so extra calls to this
131
 * function don't hurt.  There must have been at least one line added by
132
 * pg_append_history before we'll do anything.
133
 */
134
void
135
pg_send_history(PQExpBuffer history_buf)
136
0
{
137
0
#ifdef USE_READLINE
138
0
  static char *prev_hist = NULL;
139
140
0
  char     *s = history_buf->data;
141
0
  int     i;
142
143
  /* Trim any trailing \n's (OK to scribble on history_buf) */
144
0
  for (i = strlen(s) - 1; i >= 0 && s[i] == '\n'; i--)
145
0
    ;
146
0
  s[i + 1] = '\0';
147
148
0
  if (useHistory && s[0])
149
0
  {
150
0
    if (((pset.histcontrol & hctl_ignorespace) &&
151
0
       s[0] == ' ') ||
152
0
      ((pset.histcontrol & hctl_ignoredups) &&
153
0
       prev_hist && strcmp(s, prev_hist) == 0))
154
0
    {
155
      /* Ignore this line as far as history is concerned */
156
0
    }
157
0
    else
158
0
    {
159
      /* Save each previous line for ignoredups processing */
160
0
      if (prev_hist)
161
0
        free(prev_hist);
162
0
      prev_hist = pg_strdup(s);
163
      /* And send it to readline */
164
0
      add_history(s);
165
      /* Count lines added to history for use later */
166
0
      history_lines_added++;
167
0
    }
168
0
  }
169
170
0
  resetPQExpBuffer(history_buf);
171
0
#endif
172
0
}
173
174
175
/*
176
 * gets_fromFile
177
 *
178
 * Gets a line of noninteractive input from a file (which could be stdin).
179
 * The result is a malloc'd string, or NULL on EOF or input error.
180
 *
181
 * Caller *must* have set up sigint_interrupt_jmp before calling.
182
 *
183
 * Note: we re-use a static PQExpBuffer for each call.  This is to avoid
184
 * leaking memory if interrupted by SIGINT.
185
 */
186
char *
187
gets_fromFile(FILE *source)
188
8.33k
{
189
8.33k
  static PQExpBuffer buffer = NULL;
190
191
8.33k
  char    line[1024];
192
193
8.33k
  if (buffer == NULL)     /* first time through? */
194
38
    buffer = createPQExpBuffer();
195
8.29k
  else
196
8.29k
    resetPQExpBuffer(buffer);
197
198
8.33k
  for (;;)
199
8.37k
  {
200
8.37k
    char     *result;
201
202
    /* Enable SIGINT to longjmp to sigint_interrupt_jmp */
203
8.37k
    sigint_interrupt_enabled = true;
204
205
    /* Get some data */
206
8.37k
    result = fgets(line, sizeof(line), source);
207
208
    /* Disable SIGINT again */
209
8.37k
    sigint_interrupt_enabled = false;
210
211
    /* EOF or error? */
212
8.37k
    if (result == NULL)
213
72
    {
214
72
      if (ferror(source))
215
0
      {
216
0
        psql_error("could not read from input file: %s\n",
217
0
               strerror(errno));
218
0
        return NULL;
219
0
      }
220
72
      break;
221
72
    }
222
223
8.30k
    appendPQExpBufferStr(buffer, line);
224
225
8.30k
    if (PQExpBufferBroken(buffer))
226
0
    {
227
0
      psql_error("out of memory\n");
228
0
      return NULL;
229
0
    }
230
231
    /* EOL? */
232
8.30k
    if (buffer->len > 0 && buffer->data[buffer->len - 1] == '\n')
233
8.26k
    {
234
8.26k
      buffer->data[buffer->len - 1] = '\0';
235
8.26k
      return pg_strdup(buffer->data);
236
8.26k
    }
237
8.30k
  }
238
239
72
  if (buffer->len > 0)    /* EOF after reading some bufferload(s) */
240
35
    return pg_strdup(buffer->data);
241
242
  /* EOF, so return null */
243
37
  return NULL;
244
37
}
245
246
247
#ifdef USE_READLINE
248
249
/*
250
 * Macros to iterate over each element of the history list in order
251
 *
252
 * You would think this would be simple enough, but in its inimitable fashion
253
 * libedit has managed to break it: in libreadline we must use next_history()
254
 * to go from oldest to newest, but in libedit we must use previous_history().
255
 * To detect what to do, we make a trial call of previous_history(): if it
256
 * fails, then either next_history() is what to use, or there's zero or one
257
 * history entry so that it doesn't matter which direction we go.
258
 *
259
 * In case that wasn't disgusting enough: the code below is not as obvious as
260
 * it might appear.  In some libedit releases history_set_pos(0) fails until
261
 * at least one add_history() call has been done.  This is not an issue for
262
 * printHistory() or encode_history(), which cannot be invoked before that has
263
 * happened.  In decode_history(), that's not so, and what actually happens is
264
 * that we are sitting on the newest entry to start with, previous_history()
265
 * fails, and we iterate over all the entries using next_history().  So the
266
 * decode_history() loop iterates over the entries in the wrong order when
267
 * using such a libedit release, and if there were another attempt to use
268
 * BEGIN_ITERATE_HISTORY() before some add_history() call had happened, it
269
 * wouldn't work.  Fortunately we don't care about either of those things.
270
 *
271
 * Usage pattern is:
272
 *
273
 *    BEGIN_ITERATE_HISTORY(varname);
274
 *    {
275
 *      loop body referencing varname->line;
276
 *    }
277
 *    END_ITERATE_HISTORY();
278
 */
279
#define BEGIN_ITERATE_HISTORY(VARNAME) \
280
0
  do { \
281
0
    HIST_ENTRY *VARNAME; \
282
0
    bool    use_prev_; \
283
0
    \
284
0
    history_set_pos(0); \
285
0
    use_prev_ = (previous_history() != NULL); \
286
0
    history_set_pos(0); \
287
0
    for (VARNAME = current_history(); VARNAME != NULL; \
288
0
       VARNAME = use_prev_ ? previous_history() : next_history()) \
289
0
    { \
290
0
      (void) 0
291
292
#define END_ITERATE_HISTORY() \
293
0
    } \
294
0
  } while(0)
295
296
297
/*
298
 * Convert newlines to NL_IN_HISTORY for safe saving in readline history file
299
 */
300
static void
301
encode_history(void)
302
0
{
303
0
  BEGIN_ITERATE_HISTORY(cur_hist);
304
0
  {
305
0
    char     *cur_ptr;
306
307
    /* some platforms declare HIST_ENTRY.line as const char * */
308
0
    for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
309
0
    {
310
0
      if (*cur_ptr == '\n')
311
0
        *cur_ptr = NL_IN_HISTORY;
312
0
    }
313
0
  }
314
0
  END_ITERATE_HISTORY();
315
0
}
316
317
/*
318
 * Reverse the above encoding
319
 */
320
static void
321
decode_history(void)
322
0
{
323
0
  BEGIN_ITERATE_HISTORY(cur_hist);
324
0
  {
325
0
    char     *cur_ptr;
326
327
    /* some platforms declare HIST_ENTRY.line as const char * */
328
0
    for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
329
0
    {
330
0
      if (*cur_ptr == NL_IN_HISTORY)
331
0
        *cur_ptr = '\n';
332
0
    }
333
0
  }
334
0
  END_ITERATE_HISTORY();
335
0
}
336
#endif              /* USE_READLINE */
337
338
339
/*
340
 * Put any startup stuff related to input in here. It's good to maintain
341
 * abstraction this way.
342
 *
343
 * The only "flag" right now is 1 for use readline & history.
344
 */
345
void
346
initializeInput(int flags)
347
0
{
348
0
#ifdef USE_READLINE
349
0
  if (flags & 1)
350
0
  {
351
0
    const char *histfile;
352
0
    char    home[MAXPGPATH];
353
354
0
    useReadline = true;
355
356
    /* these two things must be done in this order: */
357
0
    initialize_readline();
358
0
    rl_initialize();
359
360
0
    useHistory = true;
361
0
    using_history();
362
0
    history_lines_added = 0;
363
364
0
    histfile = GetVariable(pset.vars, "HISTFILE");
365
366
0
    if (histfile == NULL)
367
0
    {
368
0
      char     *envhist;
369
370
0
      envhist = getenv("PSQL_HISTORY");
371
0
      if (envhist != NULL && strlen(envhist) > 0)
372
0
        histfile = envhist;
373
0
    }
374
375
0
    if (histfile == NULL)
376
0
    {
377
0
      if (get_home_path(home))
378
0
        psql_history = psprintf("%s/%s", home, PSQLHISTORY);
379
0
    }
380
0
    else
381
0
    {
382
0
      psql_history = pg_strdup(histfile);
383
0
      expand_tilde(&psql_history);
384
0
    }
385
386
0
    if (psql_history)
387
0
    {
388
0
      read_history(psql_history);
389
0
      decode_history();
390
0
    }
391
0
  }
392
0
#endif
393
394
0
  atexit(finishInput);
395
0
}
396
397
398
/*
399
 * This function saves the readline history when psql exits.
400
 *
401
 * fname: pathname of history file.  (Should really be "const char *",
402
 * but some ancient versions of readline omit the const-decoration.)
403
 *
404
 * max_lines: if >= 0, limit history file to that many entries.
405
 */
406
#ifdef USE_READLINE
407
static bool
408
saveHistory(char *fname, int max_lines)
409
0
{
410
0
  int     errnum;
411
412
  /*
413
   * Suppressing the write attempt when HISTFILE is set to /dev/null may
414
   * look like a negligible optimization, but it's necessary on e.g. macOS,
415
   * where write_history will fail because it tries to chmod the target
416
   * file.
417
   */
418
0
  if (strcmp(fname, DEVNULL) != 0)
419
0
  {
420
    /*
421
     * Encode \n, since otherwise readline will reload multiline history
422
     * entries as separate lines.  (libedit doesn't really need this, but
423
     * we do it anyway since it's too hard to tell which implementation we
424
     * are using.)
425
     */
426
0
    encode_history();
427
428
    /*
429
     * On newer versions of libreadline, truncate the history file as
430
     * needed and then append what we've added.  This avoids overwriting
431
     * history from other concurrent sessions (although there are still
432
     * race conditions when two sessions exit at about the same time). If
433
     * we don't have those functions, fall back to write_history().
434
     */
435
0
#if defined(HAVE_HISTORY_TRUNCATE_FILE) && defined(HAVE_APPEND_HISTORY)
436
0
    {
437
0
      int     nlines;
438
0
      int     fd;
439
440
      /* truncate previous entries if needed */
441
0
      if (max_lines >= 0)
442
0
      {
443
0
        nlines = Max(max_lines - history_lines_added, 0);
444
0
        (void) history_truncate_file(fname, nlines);
445
0
      }
446
      /* append_history fails if file doesn't already exist :-( */
447
0
      fd = open(fname, O_CREAT | O_WRONLY | PG_BINARY, 0600);
448
0
      if (fd >= 0)
449
0
        close(fd);
450
      /* append the appropriate number of lines */
451
0
      if (max_lines >= 0)
452
0
        nlines = Min(max_lines, history_lines_added);
453
0
      else
454
0
        nlines = history_lines_added;
455
0
      errnum = append_history(nlines, fname);
456
0
      if (errnum == 0)
457
0
        return true;
458
0
    }
459
#else             /* don't have append support */
460
    {
461
      /* truncate what we have ... */
462
      if (max_lines >= 0)
463
        stifle_history(max_lines);
464
      /* ... and overwrite file.  Tough luck for concurrent sessions. */
465
      errnum = write_history(fname);
466
      if (errnum == 0)
467
        return true;
468
    }
469
#endif
470
471
0
    psql_error("could not save history to file \"%s\": %s\n",
472
0
           fname, strerror(errnum));
473
0
  }
474
0
  return false;
475
0
}
476
#endif
477
478
479
480
/*
481
 * Print history to the specified file, or to the console if fname is NULL
482
 * (psql \s command)
483
 *
484
 * We used to use saveHistory() for this purpose, but that doesn't permit
485
 * use of a pager; moreover libedit's implementation behaves incompatibly
486
 * (preferring to encode its output) and may fail outright when the target
487
 * file is specified as /dev/tty.
488
 */
489
bool
490
printHistory(const char *fname, unsigned short int pager)
491
0
{
492
0
#ifdef USE_READLINE
493
0
  FILE     *output;
494
0
  bool    is_pager;
495
496
0
  if (!useHistory)
497
0
    return false;
498
499
0
  if (fname == NULL)
500
0
  {
501
    /* use pager, if enabled, when printing to console */
502
0
    output = PageOutput(INT_MAX, pager ? &(pset.popt.topt) : NULL);
503
0
    is_pager = true;
504
0
  }
505
0
  else
506
0
  {
507
0
    output = fopen(fname, "w");
508
0
    if (output == NULL)
509
0
    {
510
0
      psql_error("could not save history to file \"%s\": %s\n",
511
0
             fname, strerror(errno));
512
0
      return false;
513
0
    }
514
0
    is_pager = false;
515
0
  }
516
517
0
  BEGIN_ITERATE_HISTORY(cur_hist);
518
0
  {
519
0
    fprintf(output, "%s\n", cur_hist->line);
520
0
  }
521
0
  END_ITERATE_HISTORY();
522
523
0
  if (is_pager)
524
0
    ClosePager(output);
525
0
  else
526
0
    fclose(output);
527
528
0
  return true;
529
#else
530
  psql_error("history is not supported by this installation\n");
531
  return false;
532
#endif
533
0
}
534
535
536
static void
537
finishInput(void)
538
0
{
539
0
#ifdef USE_READLINE
540
0
  if (useHistory && psql_history)
541
0
  {
542
0
    (void) saveHistory(psql_history, pset.histsize);
543
0
    free(psql_history);
544
0
    psql_history = NULL;
545
0
  }
546
0
#endif
547
0
}