Skip to content

Commit a42c443

Browse files
committed
Fix race condition in psql \e's detection of file modification.
psql's editing commands decide whether the user has edited the file by checking for change of modification timestamp. This is probably fine for a pre-existing file, but with a temporary file that is created within the command, it's possible for a fast typist to save-and-exit in less than the one-second granularity of stat(2) timestamps. On Windows FAT filesystems the granularity is even worse, 2 seconds, making the race a bit easier to hit. To fix, try to set the temp file's mod time to be two seconds ago. It's unlikely this would fail, but then again the race condition itself is unlikely, so just ignore any error. Also, we might as well check the file size as well as its mod time. While this is a difficult bug to hit, it still seems worth back-patching, to ensure that users' edits aren't lost. Laurenz Albe, per gripe from Jacob Champion; based on fix suggestions from Jacob and myself Discussion: https://postgr.es/m/0ba3f2a658bac6546d9934ab6ba63a805d46a49b.camel@cybertec.at
1 parent a98e53e commit a42c443

File tree

1 file changed

+24
-3
lines changed

1 file changed

+24
-3
lines changed

src/bin/psql/command.c

+24-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#ifdef HAVE_PWD_H
1818
#include <pwd.h>
1919
#endif
20+
#include <utime.h>
2021
#ifndef WIN32
2122
#include <sys/types.h> /* for umask() */
2223
#include <sys/stat.h> /* for stat() */
@@ -2413,7 +2414,6 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
24132414
const char *fname;
24142415
bool error = false;
24152416
int fd;
2416-
24172417
struct stat before,
24182418
after;
24192419

@@ -2438,13 +2438,13 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
24382438
!ret ? strerror(errno) : "");
24392439
return false;
24402440
}
2441+
#endif
24412442

24422443
/*
24432444
* No canonicalize_path() here. EDIT.EXE run from CMD.EXE prepends the
24442445
* current directory to the supplied path unless we use only
24452446
* backslashes, so we do that.
24462447
*/
2447-
#endif
24482448
#ifndef WIN32
24492449
snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d.sql", tmpdir,
24502450
"/", (int) getpid());
@@ -2493,6 +2493,24 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
24932493
psql_error("%s: %s\n", fname, strerror(errno));
24942494
error = true;
24952495
}
2496+
else
2497+
{
2498+
struct utimbuf ut;
2499+
2500+
/*
2501+
* Try to set the file modification time of the temporary file
2502+
* a few seconds in the past. Otherwise, the low granularity
2503+
* (one second, or even worse on some filesystems) that we can
2504+
* portably measure with stat(2) could lead us to not
2505+
* recognize a modification, if the user typed very quickly.
2506+
*
2507+
* This is a rather unlikely race condition, so don't error
2508+
* out if the utime(2) call fails --- that would make the cure
2509+
* worse than the disease.
2510+
*/
2511+
ut.modtime = ut.actime = time(NULL) - 2;
2512+
(void) utime(fname, &ut);
2513+
}
24962514
}
24972515
}
24982516

@@ -2512,7 +2530,10 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
25122530
error = true;
25132531
}
25142532

2515-
if (!error && before.st_mtime != after.st_mtime)
2533+
/* file was edited if the size or modification time has changed */
2534+
if (!error &&
2535+
(before.st_size != after.st_size ||
2536+
before.st_mtime != after.st_mtime))
25162537
{
25172538
stream = fopen(fname, PG_BINARY_R);
25182539
if (!stream)

0 commit comments

Comments
 (0)