[189] Mosquitto database corrupted on power-loss. (#206)
Mosquitto database writes are not atomic and if power is lost during a write the file will be permanently lost. This commit makes writes as atomic as possible. Signed-off-by: Keegan Callin <kc@kcallin.net> Bug: https://github.com/eclipse/mosquitto/issues/189
This commit is contained in:
parent
ba2de88790
commit
7ba3f3d33b
@ -352,9 +352,6 @@ int mqtt3_db_backup(struct mosquitto_db *db, bool shutdown)
|
|||||||
char err[256];
|
char err[256];
|
||||||
char *outfile = NULL;
|
char *outfile = NULL;
|
||||||
int len;
|
int len;
|
||||||
#ifndef WIN32
|
|
||||||
int dir_fd;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(!db || !db->config || !db->config->persistence_filepath) return MOSQ_ERR_INVAL;
|
if(!db || !db->config || !db->config->persistence_filepath) return MOSQ_ERR_INVAL;
|
||||||
_mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Saving in-memory database to %s.", db->config->persistence_filepath);
|
_mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Saving in-memory database to %s.", db->config->persistence_filepath);
|
||||||
@ -367,6 +364,36 @@ int mqtt3_db_backup(struct mosquitto_db *db, bool shutdown)
|
|||||||
}
|
}
|
||||||
snprintf(outfile, len, "%s.new", db->config->persistence_filepath);
|
snprintf(outfile, len, "%s.new", db->config->persistence_filepath);
|
||||||
outfile[len] = '\0';
|
outfile[len] = '\0';
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* If a system lost power during the rename operation at the
|
||||||
|
* end of this file the filesystem could potentially be left
|
||||||
|
* with a directory that looks like this after powerup:
|
||||||
|
*
|
||||||
|
* 24094 -rw-r--r-- 2 root root 4099 May 30 16:27 mosquitto.db
|
||||||
|
* 24094 -rw-r--r-- 2 root root 4099 May 30 16:27 mosquitto.db.new
|
||||||
|
*
|
||||||
|
* The 24094 shows that mosquitto.db.new is hard-linked to the
|
||||||
|
* same file as mosquitto.db. If fopen(outfile, "wb") is naively
|
||||||
|
* called then mosquitto.db will be truncated and the database
|
||||||
|
* potentially corrupted.
|
||||||
|
*
|
||||||
|
* Any existing mosquitto.db.new file must be removed prior to
|
||||||
|
* opening to guarantee that it is not hard-linked to
|
||||||
|
* mosquitto.db.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
rc = unlink(outfile);
|
||||||
|
if (rc != 0) {
|
||||||
|
if (errno != ENOENT) {
|
||||||
|
_mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Error saving in-memory database, unable to remove %s.", outfile);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
db_fptr = _mosquitto_fopen(outfile, "wb");
|
db_fptr = _mosquitto_fopen(outfile, "wb");
|
||||||
if(db_fptr == NULL){
|
if(db_fptr == NULL){
|
||||||
_mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Error saving in-memory database, unable to open %s for writing.", outfile);
|
_mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Error saving in-memory database, unable to open %s for writing.", outfile);
|
||||||
@ -401,17 +428,30 @@ int mqtt3_db_backup(struct mosquitto_db *db, bool shutdown)
|
|||||||
mqtt3_db_subs_retain_write(db, db_fptr);
|
mqtt3_db_subs_retain_write(db, db_fptr);
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
fsync(fileno(db_fptr));
|
/**
|
||||||
|
*
|
||||||
|
* Closing a file does not guarantee that the contents are
|
||||||
|
* written to disk. Need to flush to send data from app to OS
|
||||||
|
* buffers, then fsync to deliver data from OS buffers to disk
|
||||||
|
* (as well as disk hardware permits).
|
||||||
|
*
|
||||||
|
* man close (http://linux.die.net/man/2/close, 2016-06-20):
|
||||||
|
*
|
||||||
|
* "successful close does not guarantee that the data has
|
||||||
|
* been successfully saved to disk, as the kernel defers
|
||||||
|
* writes. It is not common for a filesystem to flush
|
||||||
|
* the buffers when the stream is closed. If you need
|
||||||
|
* to be sure that the data is physically stored, use
|
||||||
|
* fsync(2). (It will depend on the disk hardware at this
|
||||||
|
* point."
|
||||||
|
*
|
||||||
|
* This guarantees that the new state file will not overwrite
|
||||||
|
* the old state file before its contents are valid.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
if(db->config->persistence_location){
|
fflush(db_fptr);
|
||||||
dir_fd = open(db->config->persistence_location, O_RDONLY);
|
fsync(fileno(db_fptr));
|
||||||
}else{
|
|
||||||
dir_fd = open(".", O_RDONLY);
|
|
||||||
}
|
|
||||||
if(dir_fd > 0){
|
|
||||||
fsync(dir_fd);
|
|
||||||
close(dir_fd);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
fclose(db_fptr);
|
fclose(db_fptr);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user