linux-nfs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Justin Mitchell <jumitche@redhat.com>
To: Linux NFS Mailing list <linux-nfs@vger.kernel.org>
Cc: Steve Dickson <steved@redhat.com>
Subject: [PATCH 4/5] nfs-utils: Add config file writing function
Date: Wed, 20 Jun 2018 13:13:28 +0100	[thread overview]
Message-ID: <1529496808.7473.12.camel@redhat.com> (raw)
In-Reply-To: <1529496583.7473.8.camel@redhat.com>

Adds a function to nfsconf handling to write a single config
entry, creating the file and section headers as required.

Signed-off-by: Justin Mitchell <jumitche@redhat.com>
---
 support/include/conffile.h |   1 +
 support/nfs/conffile.c     | 621 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 622 insertions(+)

diff --git a/support/include/conffile.h b/support/include/conffile.h
index bc2d61f..a3340f9 100644
--- a/support/include/conffile.h
+++ b/support/include/conffile.h
@@ -67,6 +67,7 @@ extern int      conf_match_num(const char *, const char *, int);
 extern int      conf_remove(int, const char *, const char *);
 extern int      conf_remove_section(int, const char *);
 extern void     conf_report(FILE *);
+extern int      conf_write(const char *, const char *, const char *, const char *, const char *);
 
 /*
  * Convert letter from upper case to lower case
diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c
index c7c3a62..c2dbced 100644
--- a/support/nfs/conffile.c
+++ b/support/nfs/conffile.c
@@ -1323,3 +1323,624 @@ cleanup:
 	}
 	return;
 }
+
+/* struct and queue for buffing output lines */
+TAILQ_HEAD(tailhead, outbuffer);
+
+struct outbuffer {
+	TAILQ_ENTRY(outbuffer) link;
+	char *text;
+};
+
+static struct outbuffer *
+make_outbuffer(char *line)
+{
+	struct outbuffer *new;
+
+	if (line == NULL)
+		return NULL;
+
+	new = calloc(1, sizeof(struct outbuffer));
+	if (new == NULL) {
+		xlog(L_ERROR, "malloc error creating outbuffer");
+		return NULL;
+	}
+	new->text = line;
+	return new;
+}
+
+/* compose a properly escaped tag=value line */
+static char *
+make_tagline(const char *tag, const char *value)
+{
+	char *line;
+	int ret;
+
+	if (!value)
+		return NULL;
+
+	if (should_escape(value))
+		ret = asprintf(&line, "%s = \"%s\"\n", tag, value);
+	else
+		ret = asprintf(&line, "%s = %s\n", tag, value);
+
+	if (ret == -1) {
+		xlog(L_ERROR, "malloc error composing a tag line");
+		return NULL;
+	}
+	return line;
+}
+
+/* compose a section header line */
+static char *
+make_section(const char *section, const char *arg)
+{
+	char *line;
+	int ret;
+
+	if (arg)
+		ret = asprintf(&line, "[%s \"%s\"]\n", section, arg);
+	else
+		ret = asprintf(&line, "[%s]\n", section);
+
+	if (ret == -1) {
+		xlog(L_ERROR, "malloc error composing section header");
+		return NULL;
+	}
+	return line;
+}
+
+/* does the supplied line contain the named section header */
+static bool
+is_section(const char *line, const char *section, const char *arg)
+{
+	char *end;
+	char *name;
+	char *sub;
+	bool found = false;
+
+	/* skip leading white space */
+	while (*line == '[' || isspace(*line))
+		line++;
+
+	name = strdup(line);
+	if (name == NULL) {
+		xlog_warn("conf_write: malloc failed ");
+		return false;
+	}
+
+	/* find the end */
+	end = strchr(name, ']');
+
+	/* malformed line */
+	if (end == NULL) {
+		xlog_warn("conf_write: warning: malformed section name");
+		goto cleanup;
+	}
+
+	while (*end && ( *end == ']' || isblank(*end)))
+		*(end--) = '\0';
+
+	/* is there a subsection name (aka arg) */
+	sub = strchr(name, '"');
+	if (sub) {
+		end = sub - 1;
+		*(sub++) = '\0';
+
+		/* trim whitespace between section name and arg */
+		while (end > name && isblank(*end))
+			*(end--) = '\0';
+
+		/* trim off closing quote */
+		end = strchr(sub, '"');
+		if (end == NULL) {
+			xlog_warn("conf_write: warning: malformed sub-section name");
+			goto cleanup;
+		}
+		*end = '\0';
+	}
+
+	/* ready to compare */
+	if (strcasecmp(section, name)!=0)
+		goto cleanup;
+
+	if (arg != NULL) {
+		if (sub == NULL || strcasecmp(arg, sub)!=0)
+			goto cleanup;
+	} else {
+		if (sub != NULL)
+			goto cleanup;
+	}
+
+	found = true;
+
+cleanup:
+	free(name);
+	return found;
+}
+
+/* check that line contains the specified tag assignment */
+static bool
+is_tag(const char *line, const char *tagname)
+{
+	char *end;
+	char *name;
+	bool found = false;
+
+	/* quick check, is this even an assignment line */
+	end = strchr(line, '=');
+	if (end == NULL)
+		return false;
+
+	/* skip leading white space before tag name */
+	while (isblank(*line))
+		line++;
+
+	name = strdup(line);
+	if (name == NULL) {
+		xlog_warn("conf_write: malloc failed");
+		return false;
+	}
+
+	/* trim any newline characters */
+	end = strchr(name, '\n');
+	if (end)
+		*end = '\0';
+	end = strchr(name, '\r');
+	if (end)
+		*end = '\0';
+
+	/* find the assignment equals sign */
+	end = strchr(name, '=');
+
+	/* malformed line, i swear the equals was there earlier */
+	if (end == NULL) {
+		xlog_warn("conf_write: warning: malformed tag name");
+		goto cleanup;
+	}
+
+	/* trim trailing whitespace after tag name */
+	do {
+		*(end--) = '\0';
+	}while (end > name && *end && isblank(*end));
+
+	/* quoted string, take contents of quotes only */
+	if (*name == '"') {
+		char * new = strdup(name+1);
+		end = strchr(new, '"');
+		if (end != NULL) {
+			*end = 0;
+			free(name);
+			name = new;
+		} else {
+			free(new);
+		}
+	}
+
+	/* now compare */
+	if (strcasecmp(tagname, name) == 0)
+		found = true;
+
+cleanup:
+	free(name);
+	return found;
+}
+
+/* is this an empty line ? */
+static bool
+is_empty(const char *line)
+{
+	const char *p = line;
+
+	if (line == NULL)
+		return true;
+	if (*line == '\0')
+		return true;
+
+	while (*p != '\0' && isspace(*p))
+		p++;
+
+	if (*p == '\0')
+		return true;
+
+	return false;
+}
+
+/* is this line just a comment ? */
+static bool
+is_comment(const char *line)
+{
+	if (line == NULL)
+		return false;
+
+	while (isblank(*line))
+		line++;
+
+	if (*line == '#')
+		return true;
+
+	return false;
+}
+
+/* delete a buffer queue whilst optionally outputting to file */
+static int
+flush_outqueue(struct tailhead *queue, FILE *fout)
+{
+	int ret = 0;
+	while (queue->tqh_first != NULL) {
+		struct outbuffer *ob = queue->tqh_first;
+		TAILQ_REMOVE(queue, ob, link);
+		if (ob->text) {
+			if (fout) {
+				ret = fprintf(fout, "%s", ob->text);
+				if (ret == -1) {
+					xlog(L_ERROR, "Error writing to config file: %s",
+						 strerror(errno));
+					fout = NULL;
+				}
+			}
+			free(ob->text);
+		}
+		free(ob);
+	}
+	if (ret == -1)
+		return 1;
+	return 0;
+}
+
+/* read one line of text from a file, growing the buffer as necessary */
+static int
+read_line(char **buff, int *buffsize, FILE *in)
+{
+	char *readp;
+	int used = 0;
+	bool again = false;
+
+	/* make sure we have a buffer to read into */
+	if (*buff == NULL) {
+		*buffsize = 4096;
+		*buff = calloc(1, *buffsize);
+		if (*buff == NULL) {
+			xlog(L_ERROR, "malloc error for read buffer");
+			return -1;
+		}
+	}
+
+	readp = *buff;
+
+	do {
+		int len;
+
+		/* read in a chunk */
+		if (fgets(readp, *buffsize-used, in)==NULL)
+			return -1;
+
+		len = strlen(*buff);
+		if (len == 0)
+			return -1;
+
+		/* was this the end of a line, or partial read */
+		readp = *buff + len - 1;
+
+		if (*readp != '\n' && *readp !='\r') {
+			/* no nl/cr must be partial read, go again */
+			readp++;
+			again = true;
+		} else {
+			/* that was a normal end of line */
+			again = false;
+		}
+
+		/* do we need more space */
+		if (again && *buffsize - len < 1024) {
+			int offset = readp - *buff;
+			char *newbuff;
+			*buffsize += 4096;
+			newbuff = realloc(*buff, *buffsize);
+			if (newbuff == NULL) {
+				xlog(L_ERROR, "malloc error reading line");
+				return -1;
+			}
+			*buff = newbuff;
+			readp = newbuff + offset;
+		}
+	} while(again);
+	return 0;
+}
+
+/* append a line to the given location in the queue */
+static int
+append_line(struct tailhead *queue, struct outbuffer *entry, char *line)
+{
+	int ret = 0;
+	char *end;
+	bool splitmode = false;
+	char *start = line;
+
+	if (line == NULL)
+		return -1;
+
+	/* if there are \n's in the middle of the string
+	 * then we need to split it into folded lines */
+	do {
+		char *thisline;
+		struct outbuffer *qbuff;
+
+		end = strchr(start, '\n');
+		if (end && *(end+1) != '\0') {
+			*end = '\0';
+
+			ret = asprintf(&thisline, "%s\\\n", start);
+			if (ret == -1) {
+				xlog(L_ERROR, "malloc error composing output");
+				return -1;
+			}
+			splitmode = true;
+			start = end+1;
+		} else {
+			end = NULL;
+			if (splitmode) {
+				thisline = strdup(start);
+				if (thisline == NULL)
+					return -1;
+			} else {
+				thisline = start;
+			}
+		}
+
+		qbuff = make_outbuffer(thisline);
+		if (qbuff == NULL)
+			return -1;
+
+		if (entry) {
+			TAILQ_INSERT_AFTER(queue, entry, qbuff, link);
+			entry = TAILQ_NEXT(entry, link);
+		} else {
+			TAILQ_INSERT_TAIL(queue, qbuff, link);
+		}
+	}while (end != NULL);
+
+	/* we malloced copies of this, so free the original */
+	if (splitmode)
+		free(line);
+
+	return 0;
+}
+
+/* is this a "folded" line, i.e. ends in backslash */
+static bool
+is_folded(const char *line)
+{
+	const char *end;
+	if (line == NULL)
+		return false;
+
+	end = line + strlen(line);
+	while (end > line) {
+		end--;
+		if (*end != '\n' && *end != '\r')
+			break;
+	}
+
+	if (*end == '\\')
+		return true;
+
+	return false;
+}
+
+/***
+ * Write a value to an nfs.conf style filename
+ *
+ * create the file if it doesnt already exist
+ * if value==NULL removes the setting (if present)
+ */
+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;
+	char * buff = NULL;
+	int buffsize = 0;
+
+	TAILQ_INIT(&outqueue);
+
+	if (!filename) {
+		xlog_warn("conf_write: no filename supplied");
+		return ret;
+	}
+
+	if (!section || !tag) {
+		xlog_warn("conf_write: section or tag name missing");
+		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");
+	if (!infile) {
+		if (!value) {
+			xlog_warn("conf_write: config file \"%s\" not found, nothing to do", filename);
+			ret = 0;
+			goto cleanup;
+		}
+
+		xlog_warn("conf_write: config file \"%s\" not found, creating.", filename);
+		if (append_line(&outqueue, NULL, make_section(section, arg)))
+			goto cleanup;
+
+		if (append_line(&outqueue, NULL, make_tagline(tag, value)))
+			goto cleanup;
+
+		if (flush_outqueue(&outqueue, outfile))
+			goto cleanup;
+	} else {
+		bool found = false;
+		int err = 0;
+
+		buffsize = 4096;
+		buff = calloc(1, buffsize);
+		if (buff == NULL) {
+			xlog(L_ERROR, "malloc error for read buffer");
+			goto cleanup;
+		}
+
+		buff[0] = '\0';
+		do {
+			struct outbuffer *where = NULL;
+
+			/* read in one section worth of lines */
+			do {
+				if (*buff != '\0') {
+					if (append_line(&outqueue, NULL, strdup(buff)))
+						goto cleanup;
+				}
+
+				err = read_line(&buff, &buffsize, infile);
+			} while (err == 0 && buff[0] != '[');
+
+			/* find the section header */
+			where = TAILQ_FIRST(&outqueue);
+			while (where != NULL) {
+				if (where->text != NULL && where->text[0] == '[')
+					break;
+				where = TAILQ_NEXT(where, link);
+			}
+
+			/* this is the section we care about */
+			if (where != NULL && is_section(where->text, section, arg)) {
+				/* is there an existing assignment */
+				while ((where = TAILQ_NEXT(where, link)) != NULL) {
+					if (is_tag(where->text, tag)) {
+						found = true;
+						break;
+					}
+				}
+
+				if (found) {
+					struct outbuffer *prev = TAILQ_PREV(where, tailhead, link);
+					bool again = false;
+
+					/* remove current tag */
+					do {
+						struct outbuffer *next = TAILQ_NEXT(where, link);
+						TAILQ_REMOVE(&outqueue, where, link);
+						if (is_folded(where->text))
+							again = true;
+						else
+							again = false;
+						free(where->text);
+						free(where);
+						where = next;
+					} while(again && where != NULL);
+
+					/* insert new tag */
+					if (value) {
+						if (append_line(&outqueue, 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);
+
+					/* comments immediately before a section usually relate
+					 * to the section below them */
+					while (tail != NULL && is_comment(tail->text))
+						tail = TAILQ_PREV(tail, tailhead, link);
+
+					/* there is usually blank line(s) between sections */
+					while (tail != NULL && is_empty(tail->text))
+						tail = TAILQ_PREV(tail, tailhead, link);
+
+					/* now add the tag here */
+					if (append_line(&outqueue, tail, make_tagline(tag, value)))
+						goto cleanup;
+
+					found = true;
+				}
+			}
+
+			/* 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);
+				if (tail && !is_empty(tail->text)) {
+					/* no, so add one for clarity */
+					if (append_line(&outqueue, NULL, strdup("\n")))
+						goto cleanup;
+				}
+
+				/* add the new section header */
+				if (append_line(&outqueue, NULL, make_section(section, arg)))
+					goto cleanup;
+
+				/* now add the tag */
+				if (append_line(&outqueue, NULL, make_tagline(tag, value)))
+					goto cleanup;
+			}
+
+			/* we are done with this section, write it out */
+			if (flush_outqueue(&outqueue, outfile))
+				goto cleanup;
+		} while(err == 0);
+	}
+
+	if (infile) {
+		fclose(infile);
+		infile = NULL;
+	}
+
+	fdout = -1;
+	if (fclose(outfile)) {
+		xlog(L_ERROR, "Error writing config file: %s", strerror(errno));
+		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;
+	}
+
+cleanup:
+	flush_outqueue(&outqueue, NULL);
+
+	if (buff)
+		free(buff);
+	if (infile)
+		fclose(infile);
+	if (fdout != -1)
+		close(fdout);
+	if (outpath) {
+		unlink(outpath);
+		free(outpath);
+	}
+	return ret;
+}
-- 
1.8.3.1




  parent reply	other threads:[~2018-06-20 12:13 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-06-20 12:09 [PATCH 0/5] nfs-utils: config value setting Justin Mitchell
2018-06-20 12:11 ` [PATCH 1/5] nfs-utils: Ignore empty lines in config Justin Mitchell
2018-06-20 12:12 ` [PATCH 2/5] nfs-utils: Fix comparison check for subsection headers Justin Mitchell
2018-06-20 12:12 ` [PATCH 3/5] nfs-utils: swap xlog_err for less fatal version Justin Mitchell
2018-06-20 12:13 ` Justin Mitchell [this message]
2018-06-20 12:14 ` [PATCH 5/5] nfs-utils: Add config setting to nfsconf cli tool Justin Mitchell
2018-06-22  3:18   ` Calum Mackay
2018-06-22  9:43     ` Justin Mitchell
2018-06-25 15:38 ` [PATCH 0/5] nfs-utils: config value setting Steve Dickson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1529496808.7473.12.camel@redhat.com \
    --to=jumitche@redhat.com \
    --cc=linux-nfs@vger.kernel.org \
    --cc=steved@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).