Revert to 6.0.8 behavior to save configuration file to fix "CONFIG REWRITE" when using /etc/redis.conf as new behavior expect a writable directory Revert: 90555566ed5cbd3e1c3df1293ba3bbf6098e34c3 See discussion about this breaking change in https://github.com/redis/redis/issues/8051 --- redis-6.0.9/src/config.c 2020-10-27 08:12:01.000000000 +0100 +++ redis-6.0.8/src/config.c 2020-09-10 13:09:00.000000000 +0200 @@ -1568,62 +1568,60 @@ dictReleaseIterator(di); } -/* This function replaces the old configuration file with the new content - * in an atomic manner. +/* This function overwrites the old configuration file with the new content. + * + * 1) The old file length is obtained. + * 2) If the new content is smaller, padding is added. + * 3) A single write(2) call is used to replace the content of the file. + * 4) Later the file is truncated to the length of the new content. + * + * This way we are sure the file is left in a consistent state even if the + * process is stopped between any of the four operations. * * The function returns 0 on success, otherwise -1 is returned and errno - * is set accordingly. */ + * set accordingly. */ int rewriteConfigOverwriteFile(char *configfile, sds content) { - int fd = -1; - int retval = -1; - char tmp_conffile[PATH_MAX]; - const char *tmp_suffix = ".XXXXXX"; - size_t offset = 0; - ssize_t written_bytes = 0; - - int tmp_path_len = snprintf(tmp_conffile, sizeof(tmp_conffile), "%s%s", configfile, tmp_suffix); - if (tmp_path_len <= 0 || (unsigned int)tmp_path_len >= sizeof(tmp_conffile)) { - serverLog(LL_WARNING, "Config file full path is too long"); - errno = ENAMETOOLONG; - return retval; - } - -#ifdef _GNU_SOURCE - fd = mkostemp(tmp_conffile, O_CLOEXEC); -#else - /* There's a theoretical chance here to leak the FD if a module thread forks & execv in the middle */ - fd = mkstemp(tmp_conffile); -#endif - - if (fd == -1) { - serverLog(LL_WARNING, "Could not create tmp config file (%s)", strerror(errno)); - return retval; - } - - while (offset < sdslen(content)) { - written_bytes = write(fd, content + offset, sdslen(content) - offset); - if (written_bytes <= 0) { - if (errno == EINTR) continue; /* FD is blocking, no other retryable errors */ - serverLog(LL_WARNING, "Failed after writing (%zd) bytes to tmp config file (%s)", offset, strerror(errno)); - goto cleanup; - } - offset+=written_bytes; - } - - if (fsync(fd)) - serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno)); - else if (fchmod(fd, 0644) == -1) - serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno)); - else if (rename(tmp_conffile, configfile) == -1) - serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno)); - else { - retval = 0; - serverLog(LL_DEBUG, "Rewritten config file (%s) successfully", configfile); + int retval = 0; + int fd = open(configfile,O_RDWR|O_CREAT,0644); + int content_size = sdslen(content), padding = 0; + struct stat sb; + sds content_padded; + + /* 1) Open the old file (or create a new one if it does not + * exist), get the size. */ + if (fd == -1) return -1; /* errno set by open(). */ + if (fstat(fd,&sb) == -1) { + close(fd); + return -1; /* errno set by fstat(). */ + } + + /* 2) Pad the content at least match the old file size. */ + content_padded = sdsdup(content); + if (content_size < sb.st_size) { + /* If the old file was bigger, pad the content with + * a newline plus as many "#" chars as required. */ + padding = sb.st_size - content_size; + content_padded = sdsgrowzero(content_padded,sb.st_size); + content_padded[content_size] = '\n'; + memset(content_padded+content_size+1,'#',padding-1); + } + + /* 3) Write the new content using a single write(2). */ + if (write(fd,content_padded,strlen(content_padded)) == -1) { + retval = -1; + goto cleanup; + } + + /* 4) Truncate the file to the right length if we used padding. */ + if (padding) { + if (ftruncate(fd,content_size) == -1) { + /* Non critical error... */ + } } cleanup: + sdsfree(content_padded); close(fd); - if (retval) unlink(tmp_conffile); return retval; }