[PATCH] nfs-utils: Modify nfs.conf in-place instead of replacing the file

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



When changes are written to nfs.conf the old method of building a new file
and then switching them leaves less opportunities for race conditions
against 3rd party tools but loses any existing permissions, ownerships,
attributes, etc. This patch instead uses an advisory flock to guard the
contents whilst it reads, modifies, and then rewrites the existing file.

Signed-off-by: Justin Mitchell <jumitche@xxxxxxxxxx>
---
 support/nfs/conffile.c | 134 +++++++++++++++++++++++++++----------------------
 1 file changed, 74 insertions(+), 60 deletions(-)

diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c
index 77c5790..d8f2e8e 100644
--- a/support/nfs/conffile.c
+++ b/support/nfs/conffile.c
@@ -50,6 +50,7 @@
 #include <err.h>
 #include <syslog.h>
 #include <libgen.h>
+#include <sys/file.h>
 
 #include "conffile.h"
 #include "xlog.h"
@@ -504,6 +505,17 @@ conf_readfile(const char *path)
 			return NULL;
 		}
 
+		/* Grab a shared lock to ensure its not mid-rewrite */
+		if (flock(fd, LOCK_SH)) {
+			xlog_warn("conf_readfile: attempt to grab read lock failed: %s",
+				strerror(errno));
+			goto fail;
+		}
+
+		/* only after we have the lock, check the file size ready to read it */
+		sz = lseek(fd, 0, SEEK_END);
+		lseek(fd, 0, SEEK_SET);
+
 		new_conf_addr = malloc(sz+1);
 		if (!new_conf_addr) {
 			xlog_warn("conf_readfile: malloc (%lu) failed", (unsigned long)sz);
@@ -1583,6 +1595,17 @@ flush_outqueue(struct tailhead *queue, FILE *fout)
 	return 0;
 }
 
+/* append one queue to another */
+static void
+append_queue(struct tailhead *inq, struct tailhead *outq)
+{
+	while (inq->tqh_first != NULL) {
+		struct outbuffer *ob = inq->tqh_first;
+		TAILQ_REMOVE(inq, ob, link);
+		TAILQ_INSERT_TAIL(outq, ob, link);
+	}
+}
+
 /* read one line of text from a file, growing the buffer as necessary */
 static int
 read_line(char **buff, int *buffsize, FILE *in)
@@ -1723,6 +1746,16 @@ is_folded(const char *line)
 	return false;
 }
 
+static int
+lock_file(FILE *f)
+{
+	int ret;
+	ret = flock(fileno(f), LOCK_EX);
+	if (ret) 
+		xlog(L_ERROR, "Error could not lock the file");
+	return ret;
+}
+
 /***
  * Write a value to an nfs.conf style filename
  *
@@ -1733,15 +1766,14 @@ int
 conf_write(const char *filename, const char *section, const char *arg,
 	   const char *tag, const char *value)
 {
-	int fdout = -1;
-	char *outpath = NULL;
-	FILE *outfile = NULL;
 	FILE *infile = NULL;
 	int ret = 1;
 	struct tailhead outqueue;
+	struct tailhead inqueue;
 	char * buff = NULL;
 	int buffsize = 0;
 
+	TAILQ_INIT(&inqueue);
 	TAILQ_INIT(&outqueue);
 
 	if (!filename) {
@@ -1754,26 +1786,7 @@ conf_write(const char *filename, const char *section, const char *arg,
 		return ret;
 	}
 
-	if (asprintf(&outpath, "%s.XXXXXX", filename) == -1) {
-		xlog(L_ERROR, "conf_write: error composing temp filename");
-		return ret;
-	}
-
-	fdout = mkstemp(outpath);
-	if (fdout < 0) {
-		xlog(L_ERROR, "conf_write: open temp file %s failed: %s",
-			 outpath, strerror(errno));
-		goto cleanup;
-	}
-
-	outfile = fdopen(fdout, "w");
-	if (!outfile) {
-		xlog(L_ERROR, "conf_write: fdopen temp file failed: %s",
-			 strerror(errno));
-		goto cleanup;
-	}
-
-	infile = fopen(filename, "r");
+	infile = fopen(filename, "r+");
 	if (!infile) {
 		if (!value) {
 			xlog_warn("conf_write: config file \"%s\" not found, nothing to do", filename);
@@ -1782,18 +1795,29 @@ conf_write(const char *filename, const char *section, const char *arg,
 		}
 
 		xlog_warn("conf_write: config file \"%s\" not found, creating.", filename);
-		if (append_line(&outqueue, NULL, make_section(section, arg)))
+		infile = fopen(filename, "wx");
+		if (!infile) {
+			xlog(L_ERROR, "conf_write: Error creating config file \"%s\".", filename);
+			goto cleanup;
+		}
+
+		if (lock_file(infile))
 			goto cleanup;
 
-		if (append_line(&outqueue, NULL, make_tagline(tag, value)))
+		if (append_line(&inqueue, NULL, make_section(section, arg)))
 			goto cleanup;
 
-		if (flush_outqueue(&outqueue, outfile))
+		if (append_line(&inqueue, NULL, make_tagline(tag, value)))
 			goto cleanup;
+
+		append_queue(&inqueue, &outqueue);
 	} else {
 		bool found = false;
 		int err = 0;
 
+		if (lock_file(infile))
+			goto cleanup;
+
 		buffsize = 4096;
 		buff = calloc(1, buffsize);
 		if (buff == NULL) {
@@ -1808,7 +1832,7 @@ conf_write(const char *filename, const char *section, const char *arg,
 			/* read in one section worth of lines */
 			do {
 				if (*buff != '\0') {
-					if (append_line(&outqueue, NULL, strdup(buff)))
+					if (append_line(&inqueue, NULL, strdup(buff)))
 						goto cleanup;
 				}
 
@@ -1816,7 +1840,7 @@ conf_write(const char *filename, const char *section, const char *arg,
 			} while (err == 0 && buff[0] != '[');
 
 			/* find the section header */
-			where = TAILQ_FIRST(&outqueue);
+			where = TAILQ_FIRST(&inqueue);
 			while (where != NULL) {
 				if (where->text != NULL && where->text[0] == '[')
 					break;
@@ -1864,7 +1888,7 @@ conf_write(const char *filename, const char *section, const char *arg,
 					/* remove current tag */
 					do {
 						struct outbuffer *next = TAILQ_NEXT(where, link);
-						TAILQ_REMOVE(&outqueue, where, link);
+						TAILQ_REMOVE(&inqueue, where, link);
 						if (is_folded(where->text))
 							again = true;
 						else
@@ -1876,14 +1900,14 @@ conf_write(const char *filename, const char *section, const char *arg,
 
 					/* insert new tag */
 					if (value) {
-						if (append_line(&outqueue, prev, make_tagline(tag, value)))
+						if (append_line(&inqueue, prev, make_tagline(tag, value)))
 							goto cleanup;
 					}
 				} else
 				/* no existing assignment found and we need to add one */
 				if (value) {
 					/* rewind past blank lines and comments */
-					struct outbuffer *tail = TAILQ_LAST(&outqueue, tailhead);
+					struct outbuffer *tail = TAILQ_LAST(&inqueue, tailhead);
 
 					/* comments immediately before a section usually relate
 					 * to the section below them */
@@ -1895,7 +1919,7 @@ conf_write(const char *filename, const char *section, const char *arg,
 						tail = TAILQ_PREV(tail, tailhead, link);
 
 					/* now add the tag here */
-					if (append_line(&outqueue, tail, make_tagline(tag, value)))
+					if (append_line(&inqueue, tail, make_tagline(tag, value)))
 						goto cleanup;
 
 					found = true;
@@ -1905,49 +1929,45 @@ conf_write(const char *filename, const char *section, const char *arg,
 			/* EOF and correct section not found, so add one */
 			if (err && !found && value) {
 				/* did the last section end in a blank line */
-				struct outbuffer *tail = TAILQ_LAST(&outqueue, tailhead);
+				struct outbuffer *tail = TAILQ_LAST(&inqueue, tailhead);
 				if (tail && !is_empty(tail->text)) {
 					/* no, so add one for clarity */
-					if (append_line(&outqueue, NULL, strdup("\n")))
+					if (append_line(&inqueue, NULL, strdup("\n")))
 						goto cleanup;
 				}
 
 				/* add the new section header */
-				if (append_line(&outqueue, NULL, make_section(section, arg)))
+				if (append_line(&inqueue, NULL, make_section(section, arg)))
 					goto cleanup;
 
 				/* now add the tag */
-				if (append_line(&outqueue, NULL, make_tagline(tag, value)))
+				if (append_line(&inqueue, NULL, make_tagline(tag, value)))
 					goto cleanup;
 			}
 
-			/* we are done with this section, write it out */
-			if (flush_outqueue(&outqueue, outfile))
-				goto cleanup;
+			/* we are done with this section, move it to the out queue */
+			append_queue(&inqueue, &outqueue);
 		} while(err == 0);
 	}
 
-	if (infile) {
-		fclose(infile);
-		infile = NULL;
-	}
+	/* now rewind and overwrite the file with the updated data */
+	rewind(infile);
 
-	fdout = -1;
-	if (fclose(outfile)) {
-		xlog(L_ERROR, "Error writing config file: %s", strerror(errno));
+	if (ftruncate(fileno(infile), 0)) {
+		xlog(L_ERROR, "Error truncating config file");
 		goto cleanup;
 	}
 
-	/* now swap the old file for the new one */
-	if (rename(outpath, filename)) {
-		xlog(L_ERROR, "Error updating config file: %s: %s\n", filename, strerror(errno));
-		ret = 1;
-	} else {
-		ret = 0;
-		free(outpath);
-		outpath = NULL;
+	if (flush_outqueue(&outqueue, infile))
+		goto cleanup;
+
+	if (infile) {
+		fclose(infile);
+		infile = NULL;
 	}
 
+	ret = 0;
+
 cleanup:
 	flush_outqueue(&outqueue, NULL);
 
@@ -1955,11 +1975,5 @@ cleanup:
 		free(buff);
 	if (infile)
 		fclose(infile);
-	if (fdout != -1)
-		close(fdout);
-	if (outpath) {
-		unlink(outpath);
-		free(outpath);
-	}
 	return ret;
 }
-- 
1.8.3.1




[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux