Extend the nfs.conf editing code to support the inserting of comment lines, as well as file modified information so that automated setting adjustments and imports can be appropriately flagged. Signed-off-by: Alice J Mitchell <ajmitchell@xxxxxxxxxx> --- support/include/conffile.h | 2 + support/nfs/conffile.c | 195 ++++++++++++++++++++++++++++++++++++++++++++- tools/nfsconf/nfsconf.man | 7 +- tools/nfsconf/nfsconfcli.c | 12 ++- 4 files changed, 211 insertions(+), 5 deletions(-) diff --git a/support/include/conffile.h b/support/include/conffile.h index a3340f9..7d974fe 100644 --- a/support/include/conffile.h +++ b/support/include/conffile.h @@ -69,6 +69,8 @@ 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 *); +extern const char *modified_by; + /* * Convert letter from upper case to lower case */ diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c index d8f2e8e..66d4215 100644 --- a/support/nfs/conffile.c +++ b/support/nfs/conffile.c @@ -51,6 +51,7 @@ #include <syslog.h> #include <libgen.h> #include <sys/file.h> +#include <time.h> #include "conffile.h" #include "xlog.h" @@ -113,6 +114,8 @@ struct conf_binding { LIST_HEAD (conf_bindings, conf_binding) conf_bindings[256]; +const char *modified_by = NULL; + static __inline__ uint8_t conf_hash(const char *s) { @@ -1397,6 +1400,52 @@ make_section(const char *section, const char *arg) return line; } +/* compose a comment line (with or without tag) */ +static char * +make_comment(const char *tag, const char *comment) +{ + char *line; + int ret; + + if (tag == NULL || *tag == '\0') { + ret = asprintf(&line, "# %s\n", comment); + } else { + ret = asprintf(&line, "# %s: %s\n", tag, comment); + } + + if (ret == -1) { + xlog(L_ERROR, "malloc error composing header"); + return NULL; + } + + return line; +} + +/* compose a 'file modified' comment */ +static char * +make_timestamp(const char *tag, time_t when) +{ + struct tm *tstamp; + char datestr[80]; + char *result = NULL; + + tstamp = localtime(&when); + if (strftime(datestr, sizeof(datestr), "%b %d %Y %H:%M:%S", tstamp) == 0) { + xlog(L_ERROR, "error composing date"); + datestr[0] = '\0'; + } + + if (modified_by) { + char *tmpstr = NULL; + asprintf(&tmpstr, "%s on %s", modified_by, datestr); + result = make_comment(tag, tmpstr); + free(tmpstr); + } else { + result = make_comment(tag, datestr); + } + return result; +} + /* does the supplied line contain the named section header */ static bool is_section(const char *line, const char *section, const char *arg) @@ -1406,6 +1455,10 @@ is_section(const char *line, const char *section, const char *arg) char *sub; bool found = false; + /* Not a valid section name */ + if (strcmp(section, "#") == 0) + return false; + /* skip leading white space */ while (*line == '[' || isspace(*line)) line++; @@ -1569,6 +1622,54 @@ is_comment(const char *line) return false; } +/* check that line contains the specified comment header */ +static bool +is_taggedcomment(const char *line, const char *field) +{ + char *end; + char *name; + bool found = false; + + if (line == NULL) + return false; + + while (isblank(*line)) + line++; + + if (*line != '#') + return false; + + line++; + + /* quick check, is this even a likely formatted line */ + end = strchr(line, ':'); + if (end == NULL) + return false; + + /* skip leading white space before field name */ + while (isblank(*line)) + line++; + + name = strdup(line); + if (name == NULL) { + xlog_warn("conf_write: malloc failed"); + return false; + } + + /* strip trailing spaces from the name */ + end = strchr(name, ':'); + if (end) *(end--) = 0; + while (end && end > name && isblank(*end)) + *(end--)=0; + + if (strcasecmp(name, field)==0) + found = true; + + free(name); + return found; +} + + /* delete a buffer queue whilst optionally outputting to file */ static int flush_outqueue(struct tailhead *queue, FILE *fout) @@ -1772,6 +1873,7 @@ conf_write(const char *filename, const char *section, const char *arg, struct tailhead inqueue; char * buff = NULL; int buffsize = 0; + time_t now = time(NULL); TAILQ_INIT(&inqueue); TAILQ_INIT(&outqueue); @@ -1804,12 +1906,81 @@ conf_write(const char *filename, const char *section, const char *arg, if (lock_file(infile)) goto cleanup; - if (append_line(&inqueue, NULL, make_section(section, arg))) + if (strcmp(section, "#") == 0) { + if (append_line(&inqueue, NULL, make_comment(tag, value))) + goto cleanup; + } else { + if (append_line(&inqueue, NULL, make_section(section, arg))) + goto cleanup; + + if (append_line(&inqueue, NULL, make_tagline(tag, value))) + goto cleanup; + } + + append_queue(&inqueue, &outqueue); + } else + if (strcmp(section, "#") == 0) { + /* Adding a comment line */ + struct outbuffer *where = NULL; + struct outbuffer *next = NULL; + bool found = false; + int err = 0; + + if (lock_file(infile)) goto cleanup; - if (append_line(&inqueue, NULL, make_tagline(tag, value))) + buffsize = 4096; + buff = calloc(1, buffsize); + if (buff == NULL) { + xlog(L_ERROR, "malloc error for read buffer"); goto cleanup; + } + buff[0] = '\0'; + /* read in the file */ + do { + if (*buff != '\0' + && !is_taggedcomment(buff, "Modified")) { + if (append_line(&inqueue, NULL, strdup(buff))) + goto cleanup; + } + + err = read_line(&buff, &buffsize, infile); + } while (err == 0); + + /* if a tagged comment, look for an existing instance */ + if (tag && *tag != '\0') { + where = TAILQ_FIRST(&inqueue); + while (where != NULL) { + next = TAILQ_NEXT(where, link); + struct outbuffer *prev = TAILQ_PREV(where, tailhead, link); + if (is_taggedcomment(where->text, tag)) { + TAILQ_REMOVE(&inqueue, where, link); + free(where->text); + free(where); + found = true; + if (append_line(&inqueue, prev, make_comment(tag, value))) + goto cleanup; + } + where = next; + } + } + /* it wasn't tagged or we didn't find it */ + if (!found) { + /* does the file end in a blank line or a comment */ + if (!TAILQ_EMPTY(&inqueue)) { + struct outbuffer *tail = TAILQ_LAST(&inqueue, tailhead); + if (tail && !is_empty(tail->text) && !is_comment(tail->text)) { + /* no, so add one for clarity */ + if (append_line(&inqueue, NULL, strdup("\n"))) + goto cleanup; + } + } + /* add the new comment line */ + if (append_line(&inqueue, NULL, make_comment(tag, value))) + goto cleanup; + } + /* move everything over to the outqueue for writing */ append_queue(&inqueue, &outqueue); } else { bool found = false; @@ -1831,7 +2002,8 @@ conf_write(const char *filename, const char *section, const char *arg, /* read in one section worth of lines */ do { - if (*buff != '\0') { + if (*buff != '\0' + && !is_taggedcomment(buff, "Modified")) { if (append_line(&inqueue, NULL, strdup(buff))) goto cleanup; } @@ -1950,6 +2122,23 @@ conf_write(const char *filename, const char *section, const char *arg, } while(err == 0); } + if (modified_by) { + /* check for and update the Modified header */ + /* does the file end in a blank line or a comment */ + if (!TAILQ_EMPTY(&outqueue)) { + struct outbuffer *tail = TAILQ_LAST(&outqueue, tailhead); + if (tail && !is_empty(tail->text) && !is_comment(tail->text)) { + /* no, so add one for clarity */ + if (append_line(&outqueue, NULL, strdup("\n"))) + goto cleanup; + } + } + + /* now append the modified date comment */ + if (append_line(&outqueue, NULL, make_timestamp("Modified", now))) + goto cleanup; + } + /* now rewind and overwrite the file with the updated data */ rewind(infile); diff --git a/tools/nfsconf/nfsconf.man b/tools/nfsconf/nfsconf.man index 1ae8543..3079198 100644 --- a/tools/nfsconf/nfsconf.man +++ b/tools/nfsconf/nfsconf.man @@ -31,6 +31,8 @@ nfsconf \- Query various NFS configuration settings .P .B nfsconf \-\-set .RB [ \-v | \-\-verbose ] +.RB [ \-m | \-\-modified +.IR "Modified by text" ] .RB [ \-f | \-\-file .IR infile.conf ] .RB [ \-a | \-\-arg @@ -61,7 +63,7 @@ Test if a specific tag has a value set. .IP "\fB\-g, \-\-get\fP" Output the current value of the specified tag. .IP "\fB\-s, \-\-set\fP" -Update or Add a tag and value to the config file, creating the file if necessary. +Update or Add a tag and value to the config file in a specified section, creating the tag, section, and file if necessary. If the section is defined as '#' then a comment is appended to the file. If a comment is set with a tag name then any exiting tagged comment with a matching name is replaced. .IP "\fB\-u, \-\-unset\fP" Remove the specified tag and its value from the config file. .SH OPTIONS @@ -77,6 +79,9 @@ Select a different config file to operate upon, default is .TP .B \-a, \-\-arg \fIsubsection\fR Select a specific sub-section +.SS Options only valid in \fB\-\-set\fR mode. +.B \-m, \-\-modified \fI"Modified by nfsconf"\fR +Set the text on the Modified date comment in the file. Set to empty to remove. .SH EXIT STATUS .SS \fB\-\-isset\fR mode In this mode the command will return success (0) if the selected tag has a value, any other exit code indicates the value is not set, or some other error has occurred. diff --git a/tools/nfsconf/nfsconfcli.c b/tools/nfsconf/nfsconfcli.c index f98d0d1..361d386 100644 --- a/tools/nfsconf/nfsconfcli.c +++ b/tools/nfsconf/nfsconfcli.c @@ -24,6 +24,7 @@ static void usage(const char *name) fprintf(stderr, " -v Increase Verbosity\n"); fprintf(stderr, " --file filename.conf Load this config file\n"); fprintf(stderr, " (Default config file: " NFS_CONFFILE "\n"); + fprintf(stderr, " --modified \"info\" Use \"info\" in file modified header\n"); fprintf(stderr, "Modes:\n"); fprintf(stderr, " --dump [outputfile]\n"); fprintf(stderr, " Outputs the configuration to the named file\n"); @@ -47,6 +48,8 @@ int main(int argc, char **argv) confmode_t mode = MODE_NONE; + modified_by = "Modified by nfsconf"; + while (1) { int c; int index = 0; @@ -59,10 +62,11 @@ int main(int argc, char **argv) {"dump", optional_argument, 0, 'd' }, {"file", required_argument, 0, 'f' }, {"verbose", no_argument, 0, 'v' }, + {"modified", required_argument, 0, 'm' }, {NULL, 0, 0, 0 } }; - c = getopt_long(argc, argv, "gsua:id::f:v", long_options, &index); + c = getopt_long(argc, argv, "gsua:id::f:vm:", long_options, &index); if (c == -1) break; switch (c) { @@ -99,6 +103,12 @@ int main(int argc, char **argv) mode = MODE_DUMP; dumpfile = optarg; break; + case 'm': + if (optarg == NULL || *optarg == 0) + modified_by = NULL; + else + modified_by = optarg; + break; default: usage(argv[0]); return 1; -- 1.8.3.1