hx509: make file store writes atomic

Now we'll use mkostemp() and rename() into place to make
hx509_certs_store() atomic for FILE/DER-FILE/PEM-FILE stores.

This is not ideal, as it can leave temp files in place if a process
crashes in between the mkostemp() and the rename into place.

On Linux we'll eventually make use of O_TMPFILE and linkat().  The idea
will be to first create an anonymous, zero-link file in the directory
that will contain the file at the end, write the file, then linkat() the
file into place as a .new file, then rename() the .new into place.  That
will limit the amount of junk that may be left behind to just one file.
(If the linkat() fails, then unlink() the .new and try again.  If the
rename() fails that just means the caller raced with another and the
operation is complete.)

We should really make a lib/roken interface that does this.
This commit is contained in:
Nicolas Williams
2019-10-08 16:53:56 -05:00
parent 098f6480e4
commit d31dd9e00b

View File

@@ -32,6 +32,9 @@
*/
#include "hx_locl.h"
#ifndef WIN32
#include <libgen.h>
#endif
typedef enum { USE_PEM, USE_DER } outformat;
@@ -583,17 +586,56 @@ store_func(hx509_context context, void *ctx, hx509_cert c)
return 0;
}
static int
mk_temp(const char *fn, char **tfn)
{
char *ds;
int ret = -1;
#ifdef WIN32
char buf[PATH_MAX];
char *p;
*tfn = NULL;
if ((ds = _fullpath(buf, fn, sizeof(buf))) == NULL) {
errno = errno ? errno : ENAMETOOLONG;
return -1;
}
if (p = strrchr(ds, '\\') == NULL) {
ret = asprintf(tfn, ".%s-XXXXXX", ds); /* XXX can't happen */
} else {
*(p++) = '\0';
ret = asprintf(tfn, "%s/.%s-XXXXXX", ds, p);
}
#else
*tfn = NULL;
if ((ds = strdup(fn)))
ret = asprintf(tfn, "%s/.%s-XXXXXX", dirname(ds), basename(ds));
free(ds);
#endif
/*
* Using mkostemp() risks leaving garbage files lying around. To do better
* without resorting to file locks (which have their own problems) we need
* O_TMPFILE and linkat(2), which only Linux has.
*/
return (ret == -1 || *tfn == NULL) ? -1 : mkostemp(*tfn, O_CLOEXEC);
}
static int
file_store(hx509_context context,
hx509_certs certs, void *data, int flags, hx509_lock lock)
{
struct ks_file *ksf = data;
struct store_ctx sc;
char *tfn;
int ret;
int fd;
sc.f = NULL;
fd = open(ksf->fn, O_CREAT | O_WRONLY, 0600);
fd = mk_temp(ksf->fn, &tfn);
if (fd > -1)
sc.f = fdopen(fd, "w");
if (sc.f == NULL) {
@@ -607,7 +649,15 @@ file_store(hx509_context context,
sc.format = ksf->format;
ret = hx509_certs_iter_f(context, ksf->certs, store_func, &sc);
fclose(sc.f);
if (ret == 0)
ret = fclose(sc.f);
else
(void) fclose(sc.f);
if (ret)
(void) unlink(tfn);
else
(void) rename(tfn, ksf->fn);
free(tfn);
return ret;
}