aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNRK <nrk@disroot.org>2023-05-07 23:36:17 +0200
committerNRK <nrk@disroot.org>2023-10-01 13:02:35 +0200
commit2093f36661194f6f820c1233ff8857656a4d1dfe (patch)
tree0dd8fb0e55f53223138fa44a8d96c5c93d3b9e35
parent3659361e76c5c994bee3467787c00e89780cccbc (diff)
downloadnsxiv-2093f36661194f6f820c1233ff8857656a4d1dfe.tar.zst
make thumbnail caching safer against concurrent writes
by writing to a tmpfile first, and then renaming it to the desired target - multiple nsxiv instances writing thumbnail at the same time are guaranteed not to stomp over one another. rename() is guaranteed to be atomic by POSIX. however, it can fail with EXDEV if both the files don't reside in the same filesystem. and so we cannot make the tmpfile something like "/tmp/nsxiv-XXXXXX". instead, create the tmpfile inside the cache_dir to reduce chances of EXDEV occuring.
-rw-r--r--thumbs.c17
1 files changed, 14 insertions, 3 deletions
diff --git a/thumbs.c b/thumbs.c
index 500446f..bd1e314 100644
--- a/thumbs.c
+++ b/thumbs.c
@@ -35,6 +35,8 @@
#endif
static char *cache_dir;
+static char *cache_tmpfile, *cache_tmpfile_base;
+static const char TMP_NAME[] = "/nsxiv-XXXXXX";
static char *tns_cache_filepath(const char *filepath)
{
@@ -76,6 +78,7 @@ static Imlib_Image tns_cache_load(const char *filepath, bool *outdated)
static void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
{
char *cfile, *dirend;
+ int tmpfd;
struct stat cstats, fstats;
struct utimbuf times;
Imlib_Load_Error err;
@@ -103,12 +106,17 @@ static void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
imlib_image_set_format("jpg");
imlib_image_attach_data_value("quality", NULL, 90, NULL);
}
- imlib_save_image_with_error_return(cfile, &err);
- if (err)
+ memcpy(cache_tmpfile_base, TMP_NAME, sizeof(TMP_NAME));
+ if ((tmpfd = mkstemp(cache_tmpfile)) < 0)
goto end;
+ close(tmpfd);
+ /* UPGRADE: Imlib2 v1.11.0: use imlib_save_image_fd() */
+ imlib_save_image_with_error_return(cache_tmpfile, &err);
times.actime = fstats.st_atime;
times.modtime = fstats.st_mtime;
- utime(cfile, &times);
+ utime(cache_tmpfile, &times);
+ if (err || rename(cache_tmpfile, cfile) < 0)
+ unlink(cache_tmpfile);
}
end:
free(cfile);
@@ -169,6 +177,9 @@ void tns_init(tns_t *tns, fileinfo_t *tns_files, const int *cnt, int *sel, win_t
len = strlen(homedir) + strlen(dsuffix) + strlen(s) + 1;
cache_dir = emalloc(len);
snprintf(cache_dir, len, "%s%s%s", homedir, dsuffix, s);
+ cache_tmpfile = emalloc(len + sizeof(TMP_NAME));
+ memcpy(cache_tmpfile, cache_dir, len - 1);
+ cache_tmpfile_base = cache_tmpfile + len - 1;
} else {
error(0, 0, "Cache directory not found");
}