[ofa-general] [PATCH] infiniband-diags: Add ibcli program and man page

davem at systemfabricworks.com davem at systemfabricworks.com
Wed Aug 1 07:41:58 PDT 2007



   Add ibcli program and man page to infiniband-diags.

Signed-off-by: David A. McMillen <davem at systemfabricworks.com>
---
 infiniband-diags/ChangeLog                |    4 +
 infiniband-diags/Makefile.am              |    7 +-
 infiniband-diags/man/ibcli.8              |  116 +++++
 infiniband-diags/src/ibcli.c              |  740 +++++++++++++++++++++++++++++
 4 files changed, 865 insertions(+), 2 deletions(-)

diff --git a/infiniband-diags/ChangeLog b/infiniband-diags/ChangeLog
index ad35140..a2d0563 100644
--- a/infiniband-diags/ChangeLog
+++ b/infiniband-diags/ChangeLog
@@ -1,3 +1,7 @@
+2007-07-31 Dave McMillen <davem at systemfabricworks.com>
+
+	* src/ibcli.c, man/ibcli.8: Add program and man page
+
 2007-07-10 Hal Rosenstock <halr at voltaire.com>
 
 	* 1.3.1 release of infiniband-diags
diff --git a/infiniband-diags/Makefile.am b/infiniband-diags/Makefile.am
index c90fed1..3ce38b2 100644
--- a/infiniband-diags/Makefile.am
+++ b/infiniband-diags/Makefile.am
@@ -15,7 +15,7 @@ endif
 sbin_PROGRAMS = src/ibaddr src/ibnetdiscover src/ibping src/ibportstate \
 	        src/ibroute src/ibstat src/ibsysstat src/ibtracert \
 	        src/perfquery src/sminfo src/smpdump src/smpquery \
-	        src/saquery src/vendstat
+	        src/saquery src/vendstat src/ibcli
 
 sbin_SCRIPTS = scripts/ibcheckerrs scripts/ibchecknet scripts/ibchecknode \
                scripts/ibcheckport scripts/ibhosts scripts/ibstatus \
@@ -133,6 +133,9 @@ src_vendstat_CFLAGS = -Wall $(DBGFLAGS)
 #		      $(libdir)/libibmad.la
 #src_vendstat_LDFLAGS = -Wl,--rpath -Wl,$(libdir)
 
+src_ibcli_SOURCES = src/ibcli.c
+src_ibcli_CFLAGS = -Wall $(DBGFLAGS)
+
 #src_mcm_rereg_test_SOURCES = src/mcm_rereg_test.c
 #src_mcm_rereg_test_CFLAGS = -Wall $(DBGFLAGS)
 #sbin_PROGRAMS += src/mcm_rereg_test
@@ -149,7 +152,7 @@ man_MANS = man/ibaddr.8 man/ibcheckerrors.8 man/ibcheckerrs.8 \
 	man/iblinkinfo.8 man/ibqueryerrors.8 man/ibswportwatch.8 \
 	man/ibprintswitch.8 man/ibprintca.8 man/ibfindnodesusing.8 \
 	man/ibdatacounts.8 man/ibdatacounters.8 \
-	man/ibrouters.8 man/ibprintrt.8 man/ibidsverify.8
+	man/ibrouters.8 man/ibprintrt.8 man/ibidsverify.8 man/ibcli.8
 
 EXTRA_DIST = scripts include infiniband-diags.spec.in $(man_MANS)
 
diff --git a/infiniband-diags/infiniband-diags.spec.in b/infiniband-diags/infiniband-diags.spec.in
diff --git a/infiniband-diags/man/ibcli.8 b/infiniband-diags/man/ibcli.8
new file mode 100644
index 0000000..9bc5ebe
--- /dev/null
+++ b/infiniband-diags/man/ibcli.8
@@ -0,0 +1,116 @@
+.TH IBCLI 8 "July 31, 2007" "OpenIB" "OpenIB Diagnostics"
+
+.SH NAME
+ibcli \- InfiniBand Command Line Interface
+
+.SH SYNOPSIS
+.B ibcli
+get [archive] srp|opensm|opensm.opts|sdp|openib [?|param]
+
+.B ibcli
+set [archive] srp|opensm|opensm.opts|sdp|openib param=?|value
+
+.B ibcli
+edit [archive] srp|opensm|opensm.opts|sdp|openib [editor]
+
+.B ibcli
+save archive
+
+.B ibcli
+restore archive
+
+.B ibcli
+copy source-archive archive
+
+.SH DESCRIPTION
+.PP
+ibcli is a command line interface (CLI) designed to simplify and unify
+the use of various OFED software components.  There are several parts
+to this program:
+
+.PP
+.TP
+Configuration file management/maintenance/inquiry
+
+The various configuration files are accessed through simple names
+within the CLI, referencing the actual files in the appropriate
+locations depending on where and how the OFED software was installed.
+
+Extraction and modification of individual parameter/value pairs is
+easily done, and all configuration files are modified using a rename()
+system call.  This means that either the old or new version in it's
+entirety will be seen by any program that opens and reads a file, even
+though it is unsynchronized with the modification program.  Unlike an
+editor that rewrites a file, where there is a possibility that another
+program will see some but not all changes, the method used here will
+never present an inconsistent configuration.
+
+Also, the complete set of configuration files can be saved, duplicated,
+modified separate from the "live" versions, and restored.
+
+.SH OPTIONS
+
+.PP
+.TP
+\fBget\fR
+Fetches the contents of a configuration file.  With no param specified, the
+entire contents of the file are returned.  If the param is specified as ?
+then the file is returned with comments stripped.  If a specific param is
+named, then only the line with that parameter is returned.
+
+.TP
+\fBset\fR
+Edits the contents of a configuration file.  The line with the specific named
+parameter is modified to contain the new value specified.  If the new value is
+specified as ? then no modification takes place, but the comments leading up
+to the parameter are displayed.
+
+.TP
+\fBedit\fR
+Edits the contents of a configuration file using an external editor.  The
+specified configuration file is copied to a temporary file for editing.  If
+the editor parameter was specified, it is used as the name of the editor
+program.  Otherwise, if the VISUAL environment variable is set, it will be
+used as the editor program name.  Otherwise, if the EDITOR environment
+variable is set, it will be used as the editor program name.  Otherwise,
+vi is used.
+
+.TP
+\fBsave\fR
+The set of all configuration files are saved in the named archive.
+
+.TP
+\fBrestore\fR
+The set of all configuration files are restored from the named archive.
+
+.TP
+\fBcopy\fR
+The set of all configuration files in the source archive are copied to the
+named archive.
+
+.SH NOTES
+
+.PP
+Locking configuration files:  A parallel file is created with the same name as
+the file to be locked, with the CONFIG_LOCK_SUFFIX (-locked)  appended to the
+name.  This will contain the PID of the program that did the lock.  If a
+program dies holding the lock, this is detected, and the lock file removed.
+This method is reportedly broken for the general case on NFS filesystems, but
+seems to work when all locking accesses are from the same system.
+
+.PP
+Save and restore commands
+move all configuration files (as listed in the config_files array) between
+their normal location and a subdirectory in the saved config directory
+(/etc/infiniband/saved_configs).  This subdirectory name is the name of
+the archive listed in the command, and is created during a save if it
+does not exist.  If it does exist, it is renamed with a new suffix
+(~) to keep one previous copy.  If there is already a
+subdirectory with that suffix, it (and it's contents) are deleted.
+A restore will first rename each target config file with the suffix,
+deleting any previous one with that name, and put the restored copy in.
+
+.SH AUTHORS
+.TP
+Dave McMillen
+.RI < davem at systemfabricworks.com >
diff --git a/infiniband-diags/src/ibcli.c b/infiniband-diags/src/ibcli.c
new file mode 100644
index 0000000..44b762c
--- /dev/null
+++ b/infiniband-diags/src/ibcli.c
@@ -0,0 +1,740 @@
+/*
+ * Copyright (c) 2007 System Fabric Works, Inc.  All rights reserved.
+ *
+ * This software is available to you under a choice of one of two
+ * licenses.  You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available from the file
+ * COPYING in the main directory of this source tree, or the
+ * OpenIB.org BSD license below:
+ *
+ *     Redistribution and use in source and binary forms, with or
+ *     without modification, are permitted provided that the following
+ *     conditions are met:
+ *
+ *      - Redistributions of source code must retain the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer.
+ *
+ *      - Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials
+ *        provided with the distribution.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+/*	ibcli.c
+
+	This is a command line interface (CLI) designed to simplify and unify
+	the use of various OFED software components.  There are several parts
+	to this program:
+
+      * Configuration file management/maintenance/inquiry
+
+	The various configuration files are accessed through simple names
+	within the CLI, referencing the actual files in the appropriate
+	locations depending on where and how the OFED software was installed.
+
+	Extraction and modification of individual parameter/value pairs is
+	easily done, and all configuration files are modified using a rename()
+	system call.  This means that either the old or new version in it's
+	entirety will be seen by any program that opens and reads a file, even
+	though it is unsynchronized with the modification program.  Unlike an
+	editor that rewrites a file, where there is a possibility that another
+	program will see some but not all changes, the method used here will
+	never present an inconsistent configuration.
+
+	Also, the complete set of configuration files can be saved, duplicated,
+	modified separate from the "live" versions, and restored.
+
+      * 
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <dirent.h>
+#include <signal.h>
+
+#define SAVED_CONFIGS_DIR "/etc/infiniband/saved_configs"
+#define CONFIG_LOCK_SUFFIX "-locked"
+#define CONFIG_PREV_SUFFIX "~"
+#define CONFIG_NEW_SUFFIX "+"
+#define MAX_LOCK_ATTEMPTS 20
+#define DEFAULT_EDITOR "vi"
+
+#define CMD_SAVE "save"
+#define CMD_RESTORE "restore"
+#define CMD_COPY "copy"
+#define CMD_GET "get"
+#define CMD_SET "set"
+#define CMD_EDIT "edit"
+
+struct config_files {
+    char *cmd_name;	/* Name used in commands to reference this */
+    char *file_name;	/* Absolute path to configuration file */
+    int usespace;	/* Param/value pairs do not have = between them */
+    };
+
+struct config_files config_files[] = {	/* TODO: Pick up installed places */
+    {"srp", "/etc/srp_daemon.conf", -1},
+    {"opensm", "/etc/opensm.conf", 0},
+    {"opensm.opts", "", 1},
+    {"sdp", "/etc/libsdp.conf", -1},
+    {"openib", "/etc/infiniband/openib.conf", 0},
+    { NULL, NULL } };
+
+char *argv0;		/* Remembers the argv[0] for the main() program */
+
+char inbuf[16384];	/* Used for input and temporary purposes */
+char savebuf[16384];	/* Saves commentary for later printout */
+char *savebufp;		/* Remembers how far we have filled savebuf */
+
+/*  For configuration files that may be at a user-defined place, figure out
+    where they are located and return that location. */
+
+char *find_cfg_file(char *cmd_name) {
+    if (strcmp(cmd_name, "opensm.opts") == 0) {
+	return "/var/cache/osm/opensm.opts";  /* TODO: find real path value */
+    }
+    fprintf(stderr, "ERROR: %s internal error, unknown config file for %s\n",
+	argv0, cmd_name);
+    exit(2);
+}
+
+/* Find the last non-/ character in a string */
+
+char *base_pointer(char *path) {
+    int l = strlen(path);
+    while (l > 0 && path[l-1] != '/') l--;
+    return path + l;
+}
+
+/*  Return a malloc()'d string with the full file name of the configuration
+    file, given the archive name and standard running full filename */
+
+char *working_cfg_file(char *archive, char *filename) {
+    char *rv, *fv;
+    int srv;
+
+    if (archive == NULL) {
+	rv = (char *)malloc(strlen(filename) + strlen(CONFIG_PREV_SUFFIX) + 1);
+	strcpy(rv, filename);
+	return rv;
+    }
+    fv = base_pointer(filename);
+    srv = strlen(SAVED_CONFIGS_DIR) + 1 + strlen(archive) + 1 + strlen(fv)
+					+ strlen(CONFIG_PREV_SUFFIX) + 1;
+    rv = (char *)malloc(srv);
+    strcpy(rv, SAVED_CONFIGS_DIR);
+    strcat(rv, "/");
+    strcat(rv, archive);
+    strcat(rv, "/");
+    strcat(rv, fv);
+    return rv;
+}
+
+/* Tell the user how to invoke this program */
+
+void usage() {
+    int cf_index;
+    char sep = ' ';
+
+    fprintf(stderr, "Usage:\n");
+    fprintf(stderr, " %s get [archive]", argv0);
+    for (cf_index = 0; config_files[cf_index].cmd_name != NULL; cf_index++) {
+	fprintf(stderr, "%c%s", sep, config_files[cf_index].cmd_name);
+	sep = '|';
+    }
+    fprintf(stderr, " [?|param]\n");
+    sep = ' ';
+    fprintf(stderr, " %s set [archive]", argv0);
+    for (cf_index = 0; config_files[cf_index].cmd_name != NULL; cf_index++) {
+	fprintf(stderr, "%c%s", sep, config_files[cf_index].cmd_name);
+	sep = '|';
+    }
+    fprintf(stderr, " param=?|value\n");
+    sep = ' ';
+    fprintf(stderr, " %s edit [archive]", argv0);
+    for (cf_index = 0; config_files[cf_index].cmd_name != NULL; cf_index++) {
+	fprintf(stderr, "%c%s", sep, config_files[cf_index].cmd_name);
+	sep = '|';
+    }
+    fprintf(stderr, " [editor]\n");
+    fprintf(stderr, " %s save|restore archive\n", argv0);
+    fprintf(stderr, " %s copy source-archive archive\n", argv0);
+    exit(1);
+}
+
+/* Lock a configuration file.  A parallel file is created with the same name as
+   the file to be locked, with the CONFIG_LOCK_SUFFIX appended to the name.
+   This will contain the PID of the program that did the lock.  If a program
+   dies holding the lock, this is detected, and the lock file removed.  This
+   method is reportedly broken for the general case on NFS filesystems, but
+   seems to work when all locking accesses are from the same system. */
+
+void lock_config_file(char *filename) {
+    int i, rc, attempts = MAX_LOCK_ATTEMPTS;
+    long int lpid;
+    char *lfn = (char *)malloc(strlen(filename)+strlen(CONFIG_LOCK_SUFFIX) +1);
+
+    strcpy(lfn, filename);
+    strcat(lfn, CONFIG_LOCK_SUFFIX);
+    while (--attempts >= 0) {
+	i = open(lfn, O_WRONLY | O_CREAT | O_EXCL, 0644);
+	if (i != -1) {
+	    sprintf(inbuf, "%d", getpid());
+	    write(i, inbuf, strlen(inbuf));
+	    close(i);
+	    free(lfn);
+	    return;
+	}
+	if (errno != EEXIST) {
+		perror(lfn);
+		fprintf(stderr, "%s: Could not open interlock file\n", argv0);
+		free(lfn);
+		exit(3);
+	}
+	i = open(lfn, O_RDONLY);
+	if (i != -1) {
+	    rc = read(i, inbuf, sizeof(inbuf) - 1);
+	    close(i);
+	    if (rc) {
+		lpid = strtol(inbuf, NULL, 10);
+		if (kill(lpid, 0) == -1 && errno == ESRCH) unlink(lfn);
+	    }
+	}
+	sleep(1);
+    }
+    fprintf(stderr, "%s: Could not establish interlock file %s\n", argv0, lfn);
+    free(lfn);
+    exit(3);
+}
+
+/* Unlock a configuration file - simply remove the lock file */
+
+void unlock_config_file(char *filename) {
+    int i;
+    char *lfn = (char *)malloc(strlen(filename)+strlen(CONFIG_LOCK_SUFFIX) +1);
+
+    strcpy(lfn, filename);
+    strcat(lfn, CONFIG_LOCK_SUFFIX);
+    i = unlink(lfn);
+    if (i) {
+	perror(lfn);
+	fprintf(stderr, "%s: Could not remove interlock file\n", argv0);
+	exit(3);
+    }
+    free(lfn);
+}
+
+/* Routine to copy a complete set of configuration files */
+
+void copy_configs(char *source, char *destination) {
+    int cf_index;
+    char *ifn, *ofn, *tfn;
+    FILE *input, *output;
+
+    for (cf_index = 0; config_files[cf_index].cmd_name != NULL; cf_index++) {
+	ofn = config_files[cf_index].file_name;
+	if (ofn[0] == 0)
+		ofn = find_cfg_file(config_files[cf_index].cmd_name);
+	ifn = working_cfg_file(source, ofn);
+	ofn = working_cfg_file(destination, ofn);
+	tfn = (char *)malloc(strlen(ofn)
+		+ strlen(CONFIG_PREV_SUFFIX) + strlen(CONFIG_NEW_SUFFIX));
+	lock_config_file(ifn);
+	lock_config_file(ofn);
+	input = fopen(ifn, "r");
+	if (!input) {
+	    int ern = errno;
+	    unlock_config_file(ofn);
+	    unlock_config_file(ifn);
+	    free(ofn);
+	    free(ifn);
+	    if (ern == ENOENT) continue;   /* This config file not present */
+	    fprintf(stderr, "%s: Cannot open to copy from %s\n", argv0, ifn);
+	    exit(2);
+	}
+	strcpy(tfn, ofn);
+	strcat(tfn, CONFIG_PREV_SUFFIX);
+	unlink(tfn);	/* Remove any previous previous version */
+	link(ofn, tfn);	/* Create link for new previous version */
+	strcpy(tfn, ofn);
+	strcat(tfn, CONFIG_NEW_SUFFIX);
+	output = fopen(tfn, "w");
+	if (!output) {
+	    fprintf(stderr, "%s: Cannot open to write into %s\n", argv0, tfn);
+	    exit(2);
+	}
+	while (fgets(inbuf, sizeof(inbuf), input))
+	    if (fputs(inbuf, output) == EOF) {
+		fprintf(stderr, "%s: Error writing copy to %s\n", argv0, tfn);
+		exit(2);
+	    }
+	fclose(input);
+	fclose(output);
+	if (rename(tfn, ofn)) {
+	    perror(ofn);
+	    fprintf(stderr, "%s: Cannot rename temporary copy %s\n",
+					argv0, tfn);
+	    exit(2);
+	}
+	unlock_config_file(ofn);
+	unlock_config_file(ifn);
+	free(ofn);
+	free(ifn);
+    }
+}
+
+/* Main program entry point (start here) */
+
+int main(int argc, char **argv) {
+    int i, a, setting, editing, query, found, psize, cf_index;
+    int cmd_restore, cmd_copy;
+    char *archive = NULL;
+    char *use_cfg, *cp, *np, *param, *value, *tfn;
+    struct stat stb;
+    struct dirent *ne;
+    FILE *input, *output;
+    DIR *rmd;
+
+    argv0 = base_pointer(*argv);
+    if (argc < 3) usage();
+
+
+    /* save and restore commands
+     *
+     * move all configuration files (as listed in the config_files array)
+     * between their normal location and a subdirectory in the saved config
+     * directory (SAVED_CONFIGS_DIR).  This subdirectory name is the name of
+     * the archive listed in the command, and is created during a save if it
+     * does not exist.  If it does exist, it is renamed with a new suffix
+     * (CONFIG_PREV_SUFFIX) to keep one previous copy.  If there is already a
+     * subdirectory with that suffix, it (and it's contents) are deleted.
+     * A restore will first rename each target config file with the suffix,
+     * deleting any previous one with that name, and put the restored copy in.
+     */
+
+    cmd_restore = strcmp(argv[1], CMD_RESTORE) == 0;
+    cmd_copy = strcmp(argv[1], CMD_COPY) == 0;
+    setting = strcmp(argv[1], CMD_SET) == 0;
+    editing = strcmp(argv[1], CMD_EDIT) == 0;
+    if (strcmp(argv[1], CMD_SAVE) == 0 || cmd_restore || cmd_copy) {
+	if (cmd_copy) {
+	    if (argc != 4) usage();
+	    a = 3;
+	} else {
+	    if (argc != 3) usage();
+	    a = 2;
+	}
+	if (strcmp(argv[a], ".") == 0
+	 || strncmp(argv[a], "..", 2) == 0
+	 || strchr(argv[a], '/')) {
+	    fprintf(stderr, "%s: Saved configuration name \"%s\" cannot be "
+			"., start with .., or contain a / character\n",
+			argv0, argv[a]);
+	    return 2;
+	}
+	archive = (char *)malloc(strlen(SAVED_CONFIGS_DIR) + 1
+			+ strlen(argv[a]) + 1);
+	strcpy(archive, SAVED_CONFIGS_DIR);
+	strcat(archive, "/");
+	strcat(archive, argv[a]);
+	i = stat(archive, &stb);
+
+	/* restore from archive */
+
+	if (cmd_restore) {
+	    if (i || !(S_ISDIR(stb.st_mode))) {
+		fprintf(stderr,
+		    "%s: There is no saved configuration named \"%s\"\n",
+			argv0, argv[a]);
+		free(archive);
+		return 2;
+	    }
+	    copy_configs(argv[a], NULL);
+
+	/* save to archive, or copy archive-src to archive */
+
+	} else {
+	    if (cmd_copy) {	/* Validate existence of archive1 */
+		np = (char *)malloc(strlen(SAVED_CONFIGS_DIR) + 1
+			+ strlen(argv[2]) + 1);
+		strcpy(np, SAVED_CONFIGS_DIR);
+		strcat(np, "/");
+		strcat(np, argv[2]);
+		if (stat(np, &stb) || !(S_ISDIR(stb.st_mode))) {
+		    fprintf(stderr,
+			"%s: There is no saved configuration named \"%s\"\n",
+				argv0, argv[2]);
+		    free(np);
+		    return 2;
+		}
+		free(np);
+	    }
+	    if (!i) {	/* create suffixed directory if previous save done */
+		cp = (char *)malloc(strlen(archive) + 2);
+		strcpy(cp, archive);
+		strcat(cp, CONFIG_PREV_SUFFIX);
+		rmd = opendir(cp);
+		if (rmd) {
+		    while ((ne = readdir(rmd))) {
+			if (strcmp(ne->d_name, ".") == 0
+			 || strcmp(ne->d_name, "..") == 0) continue;
+			np = (char *)malloc(strlen(cp) + 1
+					+ strlen(ne->d_name) + 1);
+			strcpy(np, cp);
+			strcat(np, "/");
+			strcat(np, ne->d_name);
+			unlink(np);   /* Errors will report with rmdir() */
+			free(np);
+		    }
+		    closedir(rmd);
+		    errno = ENOENT;
+		    rmdir(cp);
+		}
+		if (errno != ENOENT) {
+		    perror(cp);
+		    fprintf(stderr, "%s: Cannot remove backup copy\n",
+			    argv0);
+		    free(cp);
+		    free(archive);
+		    return 2;
+		}
+		i = rename(archive, cp);
+		if (i) {
+		    perror(archive);
+		    fprintf(stderr,
+			"%s: Cannot rename existing copy to backup copy\n",
+			    argv0);
+		    free(cp);
+		    free(archive);
+		    return 2;
+		}
+		free(cp);
+	    }
+	    for (cf_index=0; config_files[cf_index].cmd_name!=NULL; cf_index++)
+		if (strcmp(argv[a], config_files[cf_index].cmd_name) == 0) {
+		    fprintf(stderr, "%s: Cannot use reserved name \"%s\" for"
+					" a saved archive\n", argv0, argv[a]);
+		    return 2;
+		}
+	    i = mkdir(archive, 0755);
+	    if (i) {
+		mkdir(SAVED_CONFIGS_DIR, 0755);  /* In case it is missing */
+		i = mkdir(archive, 0755);
+	    }
+	    if (i) {
+		perror(archive);
+		fprintf(stderr, "%s: Cannot create directory for saving\n",
+			    argv0);
+		free(archive);
+		return 2;
+	    }
+	    if (cmd_copy)
+		copy_configs(argv[2], argv[a]);
+	    else
+		copy_configs(NULL, argv[a]);
+	}
+	free(archive);
+
+
+    /* set and get commands
+     */
+
+    } else if (setting || strcmp(argv[1], CMD_GET) == 0 || editing) {
+	for (a = 2; a <= 3; a++) {
+	    if (argc <= a) usage();
+	    for (cf_index=0; config_files[cf_index].cmd_name!=NULL; cf_index++)
+		if (strcmp(argv[a], config_files[cf_index].cmd_name) == 0)
+		    break;
+	    if (config_files[cf_index].cmd_name != NULL) break;
+	    archive = argv[a];
+	}
+	if (config_files[cf_index].cmd_name == NULL) usage();
+	use_cfg = config_files[cf_index].file_name;
+	if (use_cfg[0] == 0)
+	    use_cfg = find_cfg_file(config_files[cf_index].cmd_name);
+	use_cfg = working_cfg_file(archive, use_cfg);
+
+	/* get with no parameter means get whole file */
+
+	if (!editing && argc <= (a+1)) {
+	    if (setting) usage();
+	    input = fopen(use_cfg, "r");
+	    if (input == NULL) {
+		if (errno == ENOENT)
+		    fprintf(stderr,
+			"%s: There is no saved configuration named \"%s\"\n",
+			argv0, archive);
+		else
+		    perror(use_cfg);
+		return 2;
+	    }
+	    while (fgets(inbuf, sizeof(inbuf), input))
+		fputs(inbuf, stdout);
+	    fclose(input);
+	    free(use_cfg);
+	    return 0;
+	}
+
+	/* Verify that we can use simple parameter/value parsing on this */
+
+	if (!editing && config_files[cf_index].usespace < 0) {
+	    if (setting)
+		fprintf(stderr, "%s: %s file format too complicated, use edit "
+			"command\n", argv0, config_files[cf_index].cmd_name);
+	    else
+		fprintf(stderr, "%s: %s file format too complicated, use get "
+			"command with no param\n", argv0,
+			config_files[cf_index].cmd_name);
+	    free(use_cfg);
+	    return 2;
+	}
+	if (editing) {
+	    if (argc <= (a+1)) {
+        	if (((cp = getenv("VISUAL")) == NULL || *cp == '\0') &&
+		    ((cp = getenv("EDITOR")) == NULL || *cp == '\0'))
+		    cp = DEFAULT_EDITOR;
+		param = (char *)malloc(strlen(cp)+1);
+		strcpy(param, cp);
+	    } else {
+		if (argc != (a+2)) usage();
+		param = (char *)malloc(strlen(argv[a+1])+1);
+		strcpy(param, argv[a+1]);
+	    }
+	} else {
+	    param = (char *)malloc(strlen(argv[a+1])+1);
+	    strcpy(param, argv[a+1]);
+	}
+
+	/* if setting, figure out if new value is in the same "word" as the
+	 * parameter name with an = to delineate them
+	 */
+	if (setting) {
+	    for (cp = param; *cp ; cp++) if (*cp == '=') break;
+	    if (*cp && cp[1] == 0) *cp = 0;
+	    if (*cp) {
+		if (argc != (a+2)) usage();
+		value = cp + 1;
+	    } else {
+		if (argc == (a+2)) {  /* if no value, use ? as default */
+		    value = "?";
+		} else {	/* otherwise value follows */
+		    if (argc == (a+3)) {
+			value = argv[a+2];
+			if (*value == '=') value++;
+		    } else if (argc == (a+4) && strcmp(argv[a+2], "=") == 0) {
+			value = argv[a+3];
+		    } else
+			usage();
+		}
+	    }
+	    while (cp > param && (cp[-1] == ' ' || cp[-1] == '\t')) cp--;
+	    *cp = 0;
+	    while(*value == ' ' || *value == '\t') value++;
+
+	/* not set, must be get or edit */
+	} else {
+	    /* If doing get, make sure there are no extra arguments */
+	    if (!editing && argc != (a+2)) usage();
+	}
+
+	/* Check for query.  For get this means list all values, while for set
+	 * this means show the comments that are in the file before the value.
+	 */
+	if (setting)
+	    query = !(strcmp("?", value));
+	else
+	    query = !(strcmp("?", param));
+
+	/* Now scan the file */
+
+	found = 0;
+	input = fopen(use_cfg, "r");
+	if (!input) {
+	    fprintf(stderr, "%s: Cannot open to copy from %s\n", argv0,use_cfg);
+	    exit(2);
+	}
+
+	/* If set/edit command, we create a link to the current file as previous
+	 * version (so when we remove the current one, we have the backup), and
+	 * create a new file with a different name, which will get renamed to
+	 * the normal name at the end.
+	 */
+	if ((setting && !query) || editing) {
+	    lock_config_file(use_cfg);
+	    tfn = (char *)malloc(strlen(use_cfg)
+		    + strlen(CONFIG_PREV_SUFFIX) + strlen(CONFIG_NEW_SUFFIX));
+	    strcpy(tfn, use_cfg);
+	    strcat(tfn, CONFIG_PREV_SUFFIX);
+	    unlink(tfn);	/* Remove any previous previous version */
+	    link(use_cfg, tfn);	/* Create link for new previous version */
+	    strcpy(tfn, use_cfg);
+	    strcat(tfn, CONFIG_NEW_SUFFIX);
+	    output = fopen(tfn, "w");
+	    if (!output) {
+		unlock_config_file(use_cfg);
+		fprintf(stderr, "%s: Cannot open to write into %s\n",
+						argv0, tfn);
+		exit(2);
+	    }
+	}
+
+	/* Now we scan the file */
+
+	psize = strlen(param);
+	savebufp = savebuf;
+	while (fgets(inbuf, sizeof(inbuf), input)) {
+	    cp = inbuf + strlen(inbuf);
+
+	    /* Strip trailing NL/CR */
+
+	    while (cp > inbuf &&
+		(cp[-1] == '\n' || cp[-1] == '\r')) cp--;
+	    *cp = 0;
+
+	    /* Strip leading spaces/tabs */
+
+	    cp = inbuf;
+	    while (*cp == ' ' || *cp == '\t') cp++;
+
+	    /* See if this is a comment or empty line */
+
+	    if (*cp == '#' || *cp == 0) {
+		if (setting && query && *cp == '#') {
+		    cp++;	/* Save comments for set query output */
+		    i = strlen(cp);
+		    if (i < (sizeof(savebuf) - (savebufp - savebuf) - 2)) {
+			strcpy(savebufp, cp);
+			savebufp += i;
+			*savebufp++ = '\n';
+		    }
+		}
+
+	    /* Not a comment or empty line, must be part of a param/value */
+
+	    } else if (!editing) {
+
+		/* if get command with ? for param, show all param lines */
+
+		if (!setting && query) {
+		    puts(cp);
+
+		/* get or set with param to look for, see if it is there */
+
+		} else {
+		    if (strncmp(param, cp, psize) == 0) {
+			if (cp[psize] == 0 || cp[psize] == '='
+			 || cp[psize] == ' ' || cp[psize] == '\t') {
+
+			    found++;
+			    if (setting) {
+				if (query) {
+
+				    /* Found param, this a set query */
+
+				    *savebufp = 0;
+				    fputs(savebuf, stdout);
+				} else {
+
+				    /* Found param, this a set with value */
+
+				    np = cp + psize;
+				    while (*np == ' ' || *np == '\t') np++;
+				    if (*np == '=') {
+					np++;
+					while (*np == ' ' || *np == '\t') np++;
+				    }
+
+				    /* Put new value where old value was */
+
+				    i = (np - inbuf) + strlen(value) + 2;
+				    if (i > sizeof(inbuf)) {
+					fprintf(stderr, "%s: New value for "
+						"\"%s\" is %d bytes too long\n",
+						argv0, param,
+						(int)(sizeof(inbuf)-i));
+					exit(2);
+				    } else
+					strcpy(np, value);
+				}
+
+			    /* Found param and doing a get so just print it */
+
+			    } else {
+				puts(cp);
+			    }
+			}
+		    }
+		}
+		savebufp = savebuf;	/* Saw new param, toss old comments */
+	    }
+
+	    /* If doing set or edit, copy all lines to replacement file */
+
+	    if (editing || (setting && !query)) {
+		if (fprintf(output, "%s\n", inbuf) < 0) {
+		    unlock_config_file(use_cfg);
+		    fprintf(stderr, "%s: Error writing copy to %s\n",
+						argv0, tfn);
+		exit(2);
+		}
+	    }
+	}
+	fclose(input);
+
+	/* If doing set, output is done, rename file to make it the real one */
+
+	if ((setting && !query) || editing) {
+	    if (!found && !editing) {	/* Did not find param so we add it */
+		if (config_files[cf_index].usespace)
+		    fprintf(output, "%s %s\n", param, value);
+		else
+		    fprintf(output, "%s = %s\n", param, value);
+	    }
+	    fclose(output);
+	    if (editing) {
+		cp = (char *)malloc(strlen(param) + 2 + strlen(tfn) + 2);
+		strcpy(cp, param);
+		strcat(cp, " '");
+		strcat(cp, tfn);
+		strcat(cp, "'");
+		system(cp);
+		free(cp);
+	    }
+	    if (rename(tfn, use_cfg)) {
+		perror(use_cfg);
+		unlock_config_file(use_cfg);
+		fprintf(stderr, "%s: Cannot rename temporary copy %s\n",
+					argv0, tfn);
+		exit(2);
+	    }
+	    unlock_config_file(use_cfg);
+	    free(tfn);
+	}
+	free(param);
+	free(use_cfg);
+
+    /* Unknown command, just tell caller how to use the program */
+
+    } else
+	usage();
+
+    return 0;
+}



More information about the general mailing list