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.
This commit is contained in:
NRK 2023-05-08 03:36:17 +06:00
parent 3659361e76
commit 2093f36661

View file

@ -35,6 +35,8 @@
#endif #endif
static char *cache_dir; 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) 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) static void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
{ {
char *cfile, *dirend; char *cfile, *dirend;
int tmpfd;
struct stat cstats, fstats; struct stat cstats, fstats;
struct utimbuf times; struct utimbuf times;
Imlib_Load_Error err; 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_set_format("jpg");
imlib_image_attach_data_value("quality", NULL, 90, NULL); imlib_image_attach_data_value("quality", NULL, 90, NULL);
} }
imlib_save_image_with_error_return(cfile, &err); memcpy(cache_tmpfile_base, TMP_NAME, sizeof(TMP_NAME));
if (err) if ((tmpfd = mkstemp(cache_tmpfile)) < 0)
goto end; 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.actime = fstats.st_atime;
times.modtime = fstats.st_mtime; times.modtime = fstats.st_mtime;
utime(cfile, &times); utime(cache_tmpfile, &times);
if (err || rename(cache_tmpfile, cfile) < 0)
unlink(cache_tmpfile);
} }
end: end:
free(cfile); 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; len = strlen(homedir) + strlen(dsuffix) + strlen(s) + 1;
cache_dir = emalloc(len); cache_dir = emalloc(len);
snprintf(cache_dir, len, "%s%s%s", homedir, dsuffix, s); 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 { } else {
error(0, 0, "Cache directory not found"); error(0, 0, "Cache directory not found");
} }