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
next prev 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.