]> oss.titaniummirror.com Git - repo_shell.git/commitdiff
Add support for /etc/repo_shell.cfg
authorR. Steve McKown <rsmckown@gmail.com>
Sun, 23 Sep 2012 18:49:37 +0000 (12:49 -0600)
committerR. Steve McKown <rsmckown@gmail.com>
Sun, 23 Sep 2012 18:49:37 +0000 (12:49 -0600)
* Base configuration in /etc/repo_shell.cfg.
* Program name is now repo_shell
* Uses the inih library for ini parsing

26 files changed:
.gitignore
inih/LICENSE.txt [new file with mode: 0644]
inih/README.txt [new file with mode: 0644]
inih/cpp/INIReader.cpp [new file with mode: 0644]
inih/cpp/INIReader.h [new file with mode: 0644]
inih/cpp/INIReaderTest.cpp [new file with mode: 0644]
inih/examples/config.def [new file with mode: 0644]
inih/examples/ini_dump.c [new file with mode: 0644]
inih/examples/ini_example.c [new file with mode: 0644]
inih/examples/ini_xmacros.c [new file with mode: 0644]
inih/examples/test.ini [new file with mode: 0644]
inih/ini.c [new file with mode: 0644]
inih/ini.h [new file with mode: 0644]
inih/tests/bad_comment.ini [new file with mode: 0644]
inih/tests/bad_multi.ini [new file with mode: 0644]
inih/tests/bad_section.ini [new file with mode: 0644]
inih/tests/baseline_multi.txt [new file with mode: 0644]
inih/tests/baseline_single.txt [new file with mode: 0644]
inih/tests/bom.ini [new file with mode: 0644]
inih/tests/multi_line.ini [new file with mode: 0644]
inih/tests/normal.ini [new file with mode: 0644]
inih/tests/unittest.bat [new file with mode: 0644]
inih/tests/unittest.c [new file with mode: 0644]
inih/tests/user_error.ini [new file with mode: 0644]
repo-shell.c [deleted file]
repo_shell.c [new file with mode: 0644]

index b5dba6c01facf8392c004ba067a27cf8bc3bbc4b..baba52a44c01572a0949f279db7f65e3aa398bea 100644 (file)
@@ -1,2 +1,3 @@
-repo-shell
+repo_shell
 *.swp
+.svn
diff --git a/inih/LICENSE.txt b/inih/LICENSE.txt
new file mode 100644 (file)
index 0000000..1d31de2
--- /dev/null
@@ -0,0 +1,27 @@
+
+The "inih" library is distributed under the New BSD license:
+
+Copyright (c) 2009, Brush Technology
+All rights reserved.
+
+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.
+    * Neither the name of Brush Technology nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY BRUSH TECHNOLOGY ''AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BRUSH TECHNOLOGY BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/inih/README.txt b/inih/README.txt
new file mode 100644 (file)
index 0000000..8e5f8b1
--- /dev/null
@@ -0,0 +1,5 @@
+
+inih is a simple .INI file parser written in C, released under the New BSD
+license (see LICENSE.txt). Go to the project home page for more info:
+
+http://code.google.com/p/inih/
diff --git a/inih/cpp/INIReader.cpp b/inih/cpp/INIReader.cpp
new file mode 100644 (file)
index 0000000..62ae74f
--- /dev/null
@@ -0,0 +1,67 @@
+// Read an INI file into easy-to-access name/value pairs.
+
+#include <algorithm>
+#include <cctype>
+#include <cstdlib>
+#include "../ini.h"
+#include "INIReader.h"
+
+using std::string;
+
+INIReader::INIReader(string filename)
+{
+    _error = ini_parse(filename.c_str(), ValueHandler, this);
+}
+
+int INIReader::ParseError()
+{
+    return _error;
+}
+
+string INIReader::Get(string section, string name, string default_value)
+{
+    string key = MakeKey(section, name);
+    return _values.count(key) ? _values[key] : default_value;
+}
+
+long INIReader::GetInteger(string section, string name, long default_value)
+{
+    string valstr = Get(section, name, "");
+    const char* value = valstr.c_str();
+    char* end;
+    // This parses "1234" (decimal) and also "0x4D2" (hex)
+    long n = strtol(value, &end, 0);
+    return end > value ? n : default_value;
+}
+
+bool INIReader::GetBoolean(string section, string name, bool default_value)
+{
+    string valstr = Get(section, name, "");
+    // Convert to lower case to make string comparisons case-insensitive
+    std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
+    if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
+        return true;
+    else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
+        return false;
+    else
+        return default_value;
+}
+
+string INIReader::MakeKey(string section, string name)
+{
+    string key = section + "." + name;
+    // Convert to lower case to make section/name lookups case-insensitive
+    std::transform(key.begin(), key.end(), key.begin(), ::tolower);
+    return key;
+}
+
+int INIReader::ValueHandler(void* user, const char* section, const char* name,
+                            const char* value)
+{
+    INIReader* reader = (INIReader*)user;
+    string key = MakeKey(section, name);
+    if (reader->_values[key].size() > 0)
+        reader->_values[key] += "\n";
+    reader->_values[key] += value;
+    return 1;
+}
diff --git a/inih/cpp/INIReader.h b/inih/cpp/INIReader.h
new file mode 100644 (file)
index 0000000..b519a22
--- /dev/null
@@ -0,0 +1,48 @@
+// Read an INI file into easy-to-access name/value pairs.
+
+// inih and INIReader are released under the New BSD license (see LICENSE.txt).
+// Go to the project home page for more info:
+//
+// http://code.google.com/p/inih/
+
+#ifndef __INIREADER_H__
+#define __INIREADER_H__
+
+#include <map>
+#include <string>
+
+// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
+// for simplicity here rather than speed, but it should be pretty decent.)
+class INIReader
+{
+public:
+    // Construct INIReader and parse given filename. See ini.h for more info
+    // about the parsing.
+    INIReader(std::string filename);
+
+    // Return the result of ini_parse(), i.e., 0 on success, line number of
+    // first error on parse error, or -1 on file open error.
+    int ParseError();
+
+    // Get a string value from INI file, returning default_value if not found.
+    std::string Get(std::string section, std::string name,
+                    std::string default_value);
+
+    // Get an integer (long) value from INI file, returning default_value if
+    // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
+    long GetInteger(std::string section, std::string name, long default_value);
+
+    // Get a boolean value from INI file, returning default_value if not found or if
+    // not a valid true/false value. Valid true values are "true", "yes", "on", "1",
+    // and valid false values are "false", "no", "off", "0" (not case sensitive).
+    bool GetBoolean(std::string section, std::string name, bool default_value);
+
+private:
+    int _error;
+    std::map<std::string, std::string> _values;
+    static std::string MakeKey(std::string section, std::string name);
+    static int ValueHandler(void* user, const char* section, const char* name,
+                            const char* value);
+};
+
+#endif  // __INIREADER_H__
diff --git a/inih/cpp/INIReaderTest.cpp b/inih/cpp/INIReaderTest.cpp
new file mode 100644 (file)
index 0000000..abbf219
--- /dev/null
@@ -0,0 +1,20 @@
+// Example that shows simple usage of the INIReader class
+
+#include <iostream>
+#include "INIReader.h"
+
+int main()
+{
+    INIReader reader("../examples/test.ini");
+
+    if (reader.ParseError() < 0) {
+        std::cout << "Can't load 'test.ini'\n";
+        return 1;
+    }
+    std::cout << "Config loaded from 'test.ini': version="
+              << reader.GetInteger("protocol", "version", -1) << ", name="
+              << reader.Get("user", "name", "UNKNOWN") << ", email="
+              << reader.Get("user", "email", "UNKNOWN") << ", active="
+              << reader.GetBoolean("user", "active", true) << "\n";
+    return 0;
+}
diff --git a/inih/examples/config.def b/inih/examples/config.def
new file mode 100644 (file)
index 0000000..6113252
--- /dev/null
@@ -0,0 +1,8 @@
+// CFG(section, name, default)
+
+CFG(protocol, version, "0")
+
+CFG(user, name, "Fatty Lumpkin")
+CFG(user, email, "fatty@lumpkin.com")
+
+#undef CFG
diff --git a/inih/examples/ini_dump.c b/inih/examples/ini_dump.c
new file mode 100644 (file)
index 0000000..87253ee
--- /dev/null
@@ -0,0 +1,40 @@
+/* ini.h example that simply dumps an INI file without comments */
+
+#include <stdio.h>
+#include <string.h>
+#include "../ini.h"
+
+static int dumper(void* user, const char* section, const char* name,
+                  const char* value)
+{
+    static char prev_section[50] = "";
+
+    if (strcmp(section, prev_section)) {
+        printf("%s[%s]\n", (prev_section[0] ? "\n" : ""), section);
+        strncpy(prev_section, section, sizeof(prev_section));
+        prev_section[sizeof(prev_section) - 1] = '\0';
+    }
+    printf("%s = %s\n", name, value);
+    return 1;
+}
+
+int main(int argc, char* argv[])
+{
+    int error;
+
+    if (argc <= 1) {
+        printf("Usage: ini_dump filename.ini\n");
+        return 1;
+    }
+
+    error = ini_parse(argv[1], dumper, NULL);
+    if (error < 0) {
+        printf("Can't read '%s'!\n", argv[1]);
+        return 2;
+    }
+    else if (error) {
+        printf("Bad config file (first error on line %d)!\n", error);
+        return 3;
+    }
+    return 0;
+}
diff --git a/inih/examples/ini_example.c b/inih/examples/ini_example.c
new file mode 100644 (file)
index 0000000..0973572
--- /dev/null
@@ -0,0 +1,44 @@
+/* Example: parse a simple configuration file */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../ini.h"
+
+typedef struct
+{
+    int version;
+    const char* name;
+    const char* email;
+} configuration;
+
+static int handler(void* user, const char* section, const char* name,
+                   const char* value)
+{
+    configuration* pconfig = (configuration*)user;
+
+    #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
+    if (MATCH("protocol", "version")) {
+        pconfig->version = atoi(value);
+    } else if (MATCH("user", "name")) {
+        pconfig->name = strdup(value);
+    } else if (MATCH("user", "email")) {
+        pconfig->email = strdup(value);
+    } else {
+        return 0;  /* unknown section/name, error */
+    }
+    return 1;
+}
+
+int main(int argc, char* argv[])
+{
+    configuration config;
+
+    if (ini_parse("test.ini", handler, &config) < 0) {
+        printf("Can't load 'test.ini'\n");
+        return 1;
+    }
+    printf("Config loaded from 'test.ini': version=%d, name=%s, email=%s\n",
+        config.version, config.name, config.email);
+    return 0;
+}
diff --git a/inih/examples/ini_xmacros.c b/inih/examples/ini_xmacros.c
new file mode 100644 (file)
index 0000000..a2cab43
--- /dev/null
@@ -0,0 +1,46 @@
+/* Parse a configuration file into a struct using X-Macros */
+
+#include <stdio.h>
+#include <string.h>
+#include "../ini.h"
+
+/* define the config struct type */
+typedef struct {
+    #define CFG(s, n, default) char *s##_##n;
+    #include "config.def"
+} config;
+
+/* create one and fill in its default values */
+config Config = {
+    #define CFG(s, n, default) default,
+    #include "config.def"
+};
+
+/* process a line of the INI file, storing valid values into config struct */
+int handler(void *user, const char *section, const char *name,
+            const char *value)
+{
+    config *cfg = (config *)user;
+
+    if (0) ;
+    #define CFG(s, n, default) else if (strcmp(section, #s)==0 && \
+        strcmp(name, #n)==0) cfg->s##_##n = strdup(value);
+    #include "config.def"
+
+    return 1;
+}
+
+/* print all the variables in the config, one per line */
+void dump_config(config *cfg)
+{
+    #define CFG(s, n, default) printf("%s_%s = %s\n", #s, #n, cfg->s##_##n);
+    #include "config.def"
+}
+
+int main(int argc, char* argv[])
+{
+    if (ini_parse("test.ini", handler, &Config) < 0)
+        printf("Can't load 'test.ini', using defaults\n");
+    dump_config(&Config);
+    return 0;
+}
diff --git a/inih/examples/test.ini b/inih/examples/test.ini
new file mode 100644 (file)
index 0000000..be56ae4
--- /dev/null
@@ -0,0 +1,9 @@
+; Test config file for ini_example.c and INIReaderTest.cpp
+
+[protocol]             ; Protocol configuration
+version=6              ; IPv6
+
+[user]
+name = Bob Smith       ; Spaces around '=' are stripped
+email = bob@smith.com  ; And comments (like this) ignored
+active = true          ; Test a boolean
diff --git a/inih/ini.c b/inih/ini.c
new file mode 100644 (file)
index 0000000..15546e9
--- /dev/null
@@ -0,0 +1,158 @@
+/* inih -- simple .INI file parser
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+http://code.google.com/p/inih/
+
+*/
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "ini.h"
+
+#define MAX_LINE 200
+#define MAX_SECTION 50
+#define MAX_NAME 50
+
+/* Strip whitespace chars off end of given string, in place. Return s. */
+static char* rstrip(char* s)
+{
+    char* p = s + strlen(s);
+    while (p > s && isspace(*--p))
+        *p = '\0';
+    return s;
+}
+
+/* Return pointer to first non-whitespace char in given string. */
+static char* lskip(const char* s)
+{
+    while (*s && isspace(*s))
+        s++;
+    return (char*)s;
+}
+
+/* Return pointer to first char c or ';' comment in given string, or pointer to
+   null at end of string if neither found. ';' must be prefixed by a whitespace
+   character to register as a comment. */
+static char* find_char_or_comment(const char* s, char c)
+{
+    int was_whitespace = 0;
+    while (*s && *s != c && !(was_whitespace && *s == ';')) {
+        was_whitespace = isspace(*s);
+        s++;
+    }
+    return (char*)s;
+}
+
+/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
+static char* strncpy0(char* dest, const char* src, size_t size)
+{
+    strncpy(dest, src, size);
+    dest[size - 1] = '\0';
+    return dest;
+}
+
+/* See documentation in header file. */
+int ini_parse_file(FILE* file,
+                   int (*handler)(void*, const char*, const char*,
+                                  const char*),
+                   void* user)
+{
+    /* Uses a fair bit of stack (use heap instead if you need to) */
+    char line[MAX_LINE];
+    char section[MAX_SECTION] = "";
+    char prev_name[MAX_NAME] = "";
+
+    char* start;
+    char* end;
+    char* name;
+    char* value;
+    int lineno = 0;
+    int error = 0;
+
+    /* Scan through file line by line */
+    while (fgets(line, sizeof(line), file) != NULL) {
+        lineno++;
+
+        start = line;
+#if INI_ALLOW_BOM
+        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
+                           (unsigned char)start[1] == 0xBB &&
+                           (unsigned char)start[2] == 0xBF) {
+            start += 3;
+        }
+#endif
+        start = lskip(rstrip(start));
+
+        if (*start == ';' || *start == '#') {
+            /* Per Python ConfigParser, allow '#' comments at start of line */
+        }
+#if INI_ALLOW_MULTILINE
+        else if (*prev_name && *start && start > line) {
+            /* Non-black line with leading whitespace, treat as continuation
+               of previous name's value (as per Python ConfigParser). */
+            if (!handler(user, section, prev_name, start) && !error)
+                error = lineno;
+        }
+#endif
+        else if (*start == '[') {
+            /* A "[section]" line */
+            end = find_char_or_comment(start + 1, ']');
+            if (*end == ']') {
+                *end = '\0';
+                strncpy0(section, start + 1, sizeof(section));
+                *prev_name = '\0';
+            }
+            else if (!error) {
+                /* No ']' found on section line */
+                error = lineno;
+            }
+        }
+        else if (*start && *start != ';') {
+            /* Not a comment, must be a name[=:]value pair */
+            end = find_char_or_comment(start, '=');
+            if (*end != '=') {
+                end = find_char_or_comment(start, ':');
+            }
+            if (*end == '=' || *end == ':') {
+                *end = '\0';
+                name = rstrip(start);
+                value = lskip(end + 1);
+                end = find_char_or_comment(value, '\0');
+                if (*end == ';')
+                    *end = '\0';
+                rstrip(value);
+
+                /* Valid name[=:]value pair found, call handler */
+                strncpy0(prev_name, name, sizeof(prev_name));
+                if (!handler(user, section, name, value) && !error)
+                    error = lineno;
+            }
+            else if (!error) {
+                /* No '=' or ':' found on name[=:]value line */
+                error = lineno;
+            }
+        }
+    }
+
+    return error;
+}
+
+/* See documentation in header file. */
+int ini_parse(const char* filename,
+              int (*handler)(void*, const char*, const char*, const char*),
+              void* user)
+{
+    FILE* file;
+    int error;
+
+    file = fopen(filename, "r");
+    if (!file)
+        return -1;
+    error = ini_parse_file(file, handler, user);
+    fclose(file);
+    return error;
+}
diff --git a/inih/ini.h b/inih/ini.h
new file mode 100644 (file)
index 0000000..f337a6a
--- /dev/null
@@ -0,0 +1,61 @@
+/* inih -- simple .INI file parser
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+http://code.google.com/p/inih/
+
+*/
+
+#ifndef __INI_H__
+#define __INI_H__
+
+/* Make this header file easier to include in C++ code */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* Parse given INI-style file. May have [section]s, name=value pairs
+   (whitespace stripped), and comments starting with ';' (semicolon). Section
+   is "" if name=value pair parsed before any section heading. name:value
+   pairs are also supported as a concession to Python's ConfigParser.
+
+   For each name=value pair parsed, call handler function with given user
+   pointer as well as section, name, and value (data only valid for duration
+   of handler call). Handler should return nonzero on success, zero on error.
+
+   Returns 0 on success, line number of first error on parse error (doesn't
+   stop on first error), or -1 on file open error.
+*/
+int ini_parse(const char* filename,
+              int (*handler)(void* user, const char* section,
+                             const char* name, const char* value),
+              void* user);
+
+/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
+   close the file when it's finished -- the caller must do that. */
+int ini_parse_file(FILE* file,
+                   int (*handler)(void* user, const char* section,
+                                  const char* name, const char* value),
+                   void* user);
+
+/* Nonzero to allow multi-line value parsing, in the style of Python's
+   ConfigParser. If allowed, ini_parse() will call the handler with the same
+   name for each subsequent line parsed. */
+#ifndef INI_ALLOW_MULTILINE
+#define INI_ALLOW_MULTILINE 1
+#endif
+
+/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
+   the file. See http://code.google.com/p/inih/issues/detail?id=21 */
+#ifndef INI_ALLOW_BOM
+#define INI_ALLOW_BOM 1
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INI_H__ */
diff --git a/inih/tests/bad_comment.ini b/inih/tests/bad_comment.ini
new file mode 100644 (file)
index 0000000..d4bab4a
--- /dev/null
@@ -0,0 +1 @@
+This is an error
diff --git a/inih/tests/bad_multi.ini b/inih/tests/bad_multi.ini
new file mode 100644 (file)
index 0000000..3ec342f
--- /dev/null
@@ -0,0 +1 @@
+  indented
diff --git a/inih/tests/bad_section.ini b/inih/tests/bad_section.ini
new file mode 100644 (file)
index 0000000..689a4e5
--- /dev/null
@@ -0,0 +1,5 @@
+[section1]
+name1=value1
+[section2
+[section3   ; comment ]
+name2=value2
diff --git a/inih/tests/baseline_multi.txt b/inih/tests/baseline_multi.txt
new file mode 100644 (file)
index 0000000..2f4e21e
--- /dev/null
@@ -0,0 +1,46 @@
+no_file.ini: e=-1 user=0
+... [section1]
+... one=This is a test;
+... two=1234;
+... [ section 2 ]
+... happy=4;
+... sad=;
+... [comment_test]
+... test1=1;2;3;
+... test2=2;3;4;this won't be a comment, needs whitespace before ';';
+... test;3=345;
+... test4=4#5#6;
+... [colon_tests]
+... Content-Type=text/html;
+... foo=bar;
+... adams=42;
+normal.ini: e=0 user=101
+... [section1]
+... name1=value1;
+... name2=value2;
+bad_section.ini: e=3 user=102
+bad_comment.ini: e=1 user=102
+... [section]
+... a=b;
+... user=parse_error;
+... c=d;
+user_error.ini: e=3 user=104
+... [section1]
+... single1=abc;
+... multi=this is a;
+... multi=multi-line value;
+... single2=xyz;
+... [section2]
+... multi=a;
+... multi=b;
+... multi=c;
+... [section3]
+... single=ghi;
+... multi=the quick;
+... multi=brown fox;
+... name=bob smith;
+multi_line.ini: e=0 user=105
+bad_multi.ini: e=1 user=105
+... [bom_section]
+... bom_name=bom_value;
+bom.ini: e=0 user=107
diff --git a/inih/tests/baseline_single.txt b/inih/tests/baseline_single.txt
new file mode 100644 (file)
index 0000000..a847fe9
--- /dev/null
@@ -0,0 +1,42 @@
+no_file.ini: e=-1 user=0
+... [section1]
+... one=This is a test;
+... two=1234;
+... [ section 2 ]
+... happy=4;
+... sad=;
+... [comment_test]
+... test1=1;2;3;
+... test2=2;3;4;this won't be a comment, needs whitespace before ';';
+... test;3=345;
+... test4=4#5#6;
+... [colon_tests]
+... Content-Type=text/html;
+... foo=bar;
+... adams=42;
+normal.ini: e=0 user=101
+... [section1]
+... name1=value1;
+... name2=value2;
+bad_section.ini: e=3 user=102
+bad_comment.ini: e=1 user=102
+... [section]
+... a=b;
+... user=parse_error;
+... c=d;
+user_error.ini: e=3 user=104
+... [section1]
+... single1=abc;
+... multi=this is a;
+... single2=xyz;
+... [section2]
+... multi=a;
+... [section3]
+... single=ghi;
+... multi=the quick;
+... name=bob smith;
+multi_line.ini: e=4 user=105
+bad_multi.ini: e=1 user=105
+... [bom_section]
+... bom_name=bom_value;
+bom.ini: e=0 user=107
diff --git a/inih/tests/bom.ini b/inih/tests/bom.ini
new file mode 100644 (file)
index 0000000..079d09b
--- /dev/null
@@ -0,0 +1,2 @@
+[bom_section]
+bom_name=bom_value
\ No newline at end of file
diff --git a/inih/tests/multi_line.ini b/inih/tests/multi_line.ini
new file mode 100644 (file)
index 0000000..b00f086
--- /dev/null
@@ -0,0 +1,15 @@
+[section1]
+single1 = abc
+multi = this is a
+        multi-line value
+single2 = xyz
+[section2]
+multi = a
+        b
+        c
+[section3]
+single: ghi
+multi: the quick
+       brown fox
+name = bob smith  ; comment line 1
+                  ; comment line 2
diff --git a/inih/tests/normal.ini b/inih/tests/normal.ini
new file mode 100644 (file)
index 0000000..bd5bcd7
--- /dev/null
@@ -0,0 +1,25 @@
+; This is an INI file
+[section1]  ; section comment
+one=This is a test  ; name=value comment
+two = 1234
+; x=y
+
+[ section 2 ]
+happy  =  4
+sad =
+
+[empty]
+; do nothing
+
+[comment_test]
+test1 = 1;2;3 ; only this will be a comment
+test2 = 2;3;4;this won't be a comment, needs whitespace before ';'
+test;3 = 345 ; key should be "test;3"
+test4 = 4#5#6 ; '#' only starts a comment at start of line
+#test5 = 567 ; entire line commented
+ # test6 = 678 ; entire line commented, except in MULTILINE mode
+
+[colon_tests]
+Content-Type: text/html
+foo:bar
+adams : 42
diff --git a/inih/tests/unittest.bat b/inih/tests/unittest.bat
new file mode 100644 (file)
index 0000000..d0e84cf
--- /dev/null
@@ -0,0 +1,2 @@
+@call tcc ..\ini.c -I..\ -run unittest.c > baseline_multi.txt
+@call tcc ..\ini.c -I..\ -DINI_ALLOW_MULTILINE=0 -run unittest.c > baseline_single.txt
diff --git a/inih/tests/unittest.c b/inih/tests/unittest.c
new file mode 100644 (file)
index 0000000..97643ca
--- /dev/null
@@ -0,0 +1,58 @@
+/* inih -- unit tests
+
+This works simply by dumping a bunch of info to standard output, which is
+redirected to an output file (baseline_*.txt) and checked into the Subversion
+repository. This baseline file is the test output, so the idea is to check it
+once, and if it changes -- look at the diff and see which tests failed.
+
+Here's how I produced the two baseline files (with Tiny C Compiler):
+
+tcc -DINI_ALLOW_MULTILINE=1 ../ini.c -run unittest.c > baseline_multi.txt
+tcc -DINI_ALLOW_MULTILINE=0 ../ini.c -run unittest.c > baseline_single.txt
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../ini.h"
+
+int User;
+char Prev_section[50];
+
+int dumper(void* user, const char* section, const char* name,
+           const char* value)
+{
+    User = (int)user;
+    if (strcmp(section, Prev_section)) {
+        printf("... [%s]\n", section);
+        strncpy(Prev_section, section, sizeof(Prev_section));
+        Prev_section[sizeof(Prev_section) - 1] = '\0';
+    }
+    printf("... %s=%s;\n", name, value);
+
+    return strcmp(name, "user")==0 && strcmp(value, "parse_error")==0 ? 0 : 1;
+}
+
+void parse(const char* fname) {
+    static int u = 100;
+    int e;
+
+    *Prev_section = '\0';
+    e = ini_parse(fname, dumper, (void*)u);
+    printf("%s: e=%d user=%d\n", fname, e, User);
+    u++;
+}
+
+int main(void)
+{
+    parse("no_file.ini");
+    parse("normal.ini");
+    parse("bad_section.ini");
+    parse("bad_comment.ini");
+    parse("user_error.ini");
+    parse("multi_line.ini");
+    parse("bad_multi.ini");
+    parse("bom.ini");
+    return 0;
+}
diff --git a/inih/tests/user_error.ini b/inih/tests/user_error.ini
new file mode 100644 (file)
index 0000000..6596387
--- /dev/null
@@ -0,0 +1,4 @@
+[section]
+a = b
+user = parse_error
+c = d
diff --git a/repo-shell.c b/repo-shell.c
deleted file mode 100644 (file)
index 8399925..0000000
+++ /dev/null
@@ -1,332 +0,0 @@
-#include <stdio.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <pwd.h>
-#include <string.h>
-
-/* TODO: these should come from a config file */
-static char *svn_repo_root = "/var/lib/svn/";
-static char *git_repo_root = "/var/lib/svn/";
-//static char *repo_user = "repo";
-static char *repo_user = "smckown";
-
-#define alloc_nr(x) (((x)+16)*3/2)
-
-/*
- * Realloc the buffer pointed at by variable 'x' so that it can hold
- * at least 'nr' entries; the number of entries currently allocated
- * is 'alloc', using the standard growing factor alloc_nr() macro.
- *
- * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'.
- */
-#define ALLOC_GROW(x, nr, alloc) \
-       do { \
-               if ((nr) > alloc) { \
-                       if (alloc_nr(alloc) < (nr)) \
-                               alloc = (nr); \
-                       else \
-                               alloc = alloc_nr(alloc); \
-                       x = xrealloc((x), alloc * sizeof(*(x))); \
-               } \
-       } while (0)
-
-static inline void die(const char *fmt, ...)
-{
-       va_list ap;
-
-       va_start(ap, fmt);
-       fprintf(stderr, "error: ");
-       vfprintf(stderr, fmt, ap);
-       fprintf(stderr, "\n" );
-       va_end(ap);
-       exit(1);
-}
-
-char *xstrdup(const char *str)
-{
-       char *ret = strdup(str);
-       if (!ret)
-               die("out of memory");
-       return ret;
-}
-
-void *xmalloc(size_t size)
-{
-       void *ret;
-
-       ret = malloc(size);
-       if (!ret && !size)
-               ret = malloc(1);
-       if (!ret)
-               die("out of memory");
-       return ret;
-}
-
-void *xrealloc(void *ptr, size_t size)
-{
-       void *ret;
-
-       ret = realloc(ptr, size);
-       if (!ret && !size)
-               ret = realloc(ptr, 1);
-       if (!ret)
-               die("Out of memory, realloc failed");
-       return ret;
-}
-
-static uid_t user_uid(char *user)
-{
-       struct passwd *pw = getpwnam(user);
-
-       if (!pw)
-               die("invalid user %s", user);
-       return pw->pw_uid;
-}
-
-static void change_user(char *user)
-{
-       /* This is the function for which setuid is required, as root */
-       setuid(user_uid(user));
-}
-
-static char *dequote(char *arg)
-{
-       char* narg = NULL;
-
-       if (arg && *arg == '\'') {
-               char* end = arg + strlen(arg) - 1;
-
-               if (end != arg && *end == '\'') {
-                       narg = arg + 1;
-                       *end = '\0';
-               }
-       }
-       return narg;
-}
-
-static char *add_prefix(char *prefix, char* arg)
-{
-       int size;
-
-       if (arg && prefix && strlen(prefix)) {
-               char *n = xmalloc(sizeof(char *) *
-                       (strlen(prefix) + strlen(arg) + 2));
-               strcpy(n, prefix);
-               strcat(n, "/");
-               strcat(n, arg);
-               arg = n;
-       }
-       return arg;
-}
-
-static int check_ssh_interactive(uid_t uid)
-{
-       /* TODO: Check the config file for the user owning uid to see if that
-        * user should be able to execute any commands other than those required
-        * to support repository access.  Return a boolean true/false.
-        */
-       return 1; /* for now */
-}
-
-static int git_check_access(const char *cmd, const char *arg, const char *user)
-{
-       /* TODO: Read some configuration file which maps users and access
-        * to a boolean true/false value.
-        *
-        * The git command can support read and write.
-        * git-receive-pack is ok for readers and writers
-        * git-upload-pack is ok only for writers
-        * git-upload-archive is ok only for writers
-        */
-       return 1; /* assume OK for now */
-}
-
-static int do_git_cmd(const char *cmd, char *arg, char *user)
-{
-       const char *nargv[4];
-       char* narg;
-       int ret;
-
-       if (!(arg = dequote(arg)))
-               die("bad argument");
-       if (strncmp(cmd, "git-", 4))
-               die("bad command");
-
-       change_user(repo_user);
-       if (!git_check_access(cmd, arg, user))
-               die("permission denied");
-
-       nargv[0] = cmd;
-       nargv[1] = add_prefix(git_repo_root, arg);
-       nargv[2] = NULL;
-
-       ret = execvp(nargv[0], (char *const *) nargv);
-       /* Code unreached if execv successful */
-       free(narg);
-       return ret;
-}
-
-static int do_svnserve_cmd(const char *cmd, char *arg, char *user)
-{
-       const char *svnserve_argv[7] = {
-               cmd, "-t", "--root", svn_repo_root, "--tunnel-user", user, NULL
-       };
-       int ret;
-
-       change_user(repo_user);
-       return execvp(svnserve_argv[0], (char *const *) svnserve_argv);
-}
-
-#define SPLIT_CMDLINE_BAD_ENDING 1
-#define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
-static const char *split_cmdline_errors[] = {
-       "cmdline ends with \\",
-       "unclosed quote"
-};
-
-int split_cmdline(char *cmdline, const char ***argv)
-{
-       int src, dst, count = 0, size = 16;
-       char quoted = 0;
-
-       *argv = xmalloc(sizeof(char *) * size);
-
-       /* split alias_string */
-       (*argv)[count++] = cmdline;
-       for (src = dst = 0; cmdline[src];) {
-               char c = cmdline[src];
-               if (!quoted && isspace(c)) {
-                       cmdline[dst++] = 0;
-                       while (cmdline[++src]
-                                       && isspace(cmdline[src]))
-                               ; /* skip */
-                       ALLOC_GROW(*argv, count+1, size);
-                       (*argv)[count++] = cmdline + dst;
-               } else if (!quoted && (c == '\'' || c == '"')) {
-                       quoted = c;
-                       src++;
-               } else if (c == quoted) {
-                       quoted = 0;
-                       src++;
-               } else {
-                       if (c == '\\' && quoted != '\'') {
-                               src++;
-                               c = cmdline[src];
-                               if (!c) {
-                                       free(*argv);
-                                       *argv = NULL;
-                                       return -SPLIT_CMDLINE_BAD_ENDING;
-                               }
-                       }
-                       cmdline[dst++] = c;
-                       src++;
-               }
-       }
-
-       cmdline[dst] = 0;
-
-       if (quoted) {
-               free(*argv);
-               *argv = NULL;
-               return -SPLIT_CMDLINE_UNCLOSED_QUOTE;
-       }
-
-       ALLOC_GROW(*argv, count+1, size);
-       (*argv)[count] = NULL;
-
-       return count;
-}
-
-const char *split_cmdline_strerror(int split_cmdline_errno) {
-       return split_cmdline_errors[-split_cmdline_errno-1];
-}
-
-static void cd_to_homedir(void)
-{
-       const char *home = getenv("HOME");
-       if (!home)
-               die("could not determine user's home directory; HOME is unset");
-       if (chdir(home) == -1)
-               die("could not chdir to user's home directory");
-}
-
-static struct commands {
-       const char *name;
-       int (*exec)(const char *cmd, char *arg, char *user);
-} cmd_list[] = {
-       { "git-receive-pack", do_git_cmd },
-       { "git-upload-pack", do_git_cmd },
-       { "git-upload-archive", do_git_cmd },
-       { "svnserve", do_svnserve_cmd },
-       { NULL },
-};
-
-int main(int argc, char **argv)
-{
-       char *prog;
-       const char **user_argv;
-       struct commands *cmd;
-       int devnull_fd;
-       int count;
-
-       /*
-        * Always open file descriptors 0/1/2 to avoid clobbering files
-        * in die().  It also avoids not messing up when the pipes are
-        * dup'ed onto stdin/stdout/stderr in the child processes we spawn.
-        */
-       devnull_fd = open("/dev/null", O_RDWR);
-       while (devnull_fd >= 0 && devnull_fd <= 2)
-               devnull_fd = dup(devnull_fd);
-       if (devnull_fd == -1)
-               die("opening /dev/null failed");
-       close (devnull_fd);
-
-       if (argc < 3)
-               die("invalid arguments");
-       fprintf(stderr, "prog |%s|\n", argv[2]);
-       prog = xstrdup(argv[2]);
-       if (!strncmp(prog, "git", 3) && isspace(prog[3]))
-               /* Accept "git foo" as if the caller said "git-foo". */
-               prog[3] = '-';
-
-       for (cmd = cmd_list ; cmd->name ; cmd++) {
-               int len = strlen(cmd->name);
-               char *arg;
-               struct passwd *pw;
-               if (strncmp(cmd->name, prog, len))
-                       continue;
-               arg = NULL;
-               switch (prog[len]) {
-               case '\0':
-                       arg = NULL;
-                       break;
-               case ' ':
-                       arg = prog + len + 1;
-                       break;
-               default:
-                       continue;
-               }
-
-               pw = getpwuid(getuid());
-               exit(cmd->exec(cmd->name, arg, pw->pw_name));
-       }
-
-       if (!check_ssh_interactive(getuid()))
-               die("only repository access is allowed");
-
-       cd_to_homedir();
-       count = split_cmdline(prog, &user_argv);
-       if (count >= 0) {
-               execvp(user_argv[0], (char *const *) user_argv);
-               free(user_argv);
-               die("unrecognized command '%s'", argv[2]);
-       } else {
-               free(prog);
-               die("invalid command format '%s': %s", argv[2],
-                   split_cmdline_strerror(count));
-       }
-}
diff --git a/repo_shell.c b/repo_shell.c
new file mode 100644 (file)
index 0000000..35edbe7
--- /dev/null
@@ -0,0 +1,370 @@
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <string.h>
+#include "ini.h"
+
+#define CFG_FILE "/etc/repo_shell.cfg"
+
+typedef struct {
+       char *svn_root;
+       char *git_root;
+       char *owner;
+} cfg_t;
+
+#undef USE_DEFAULTS
+#ifdef USE_DEFAULTS /* perhaps we want defaults?  Not sure */
+static cfg_t cfg {
+       svn_root: "/var/lib/svn/repositories",
+       git_root: "/var/lib/git",
+       owner: "repo"
+};
+#else
+static cfg_t cfg;
+#endif
+
+#define alloc_nr(x) (((x)+16)*3/2)
+
+/*
+ * Realloc the buffer pointed at by variable 'x' so that it can hold
+ * at least 'nr' entries; the number of entries currently allocated
+ * is 'alloc', using the standard growing factor alloc_nr() macro.
+ *
+ * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'.
+ */
+#define ALLOC_GROW(x, nr, alloc) \
+       do { \
+               if ((nr) > alloc) { \
+                       if (alloc_nr(alloc) < (nr)) \
+                               alloc = (nr); \
+                       else \
+                               alloc = alloc_nr(alloc); \
+                       x = xrealloc((x), alloc * sizeof(*(x))); \
+               } \
+       } while (0)
+
+static inline void die(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       fprintf(stderr, "error: ");
+       vfprintf(stderr, fmt, ap);
+       fprintf(stderr, "\n" );
+       va_end(ap);
+       exit(1);
+}
+
+char *xstrdup(const char *str)
+{
+       char *ret = strdup(str);
+       if (!ret)
+               die("out of memory");
+       return ret;
+}
+
+void *xmalloc(size_t size)
+{
+       void *ret;
+
+       ret = malloc(size);
+       if (!ret && !size)
+               ret = malloc(1);
+       if (!ret)
+               die("out of memory");
+       return ret;
+}
+
+void *xrealloc(void *ptr, size_t size)
+{
+       void *ret;
+
+       ret = realloc(ptr, size);
+       if (!ret && !size)
+               ret = realloc(ptr, 1);
+       if (!ret)
+               die("Out of memory, realloc failed");
+       return ret;
+}
+
+static uid_t user_uid(char *user)
+{
+       struct passwd *pw = getpwnam(user);
+
+       if (!pw)
+               die("invalid user %s", user);
+       return pw->pw_uid;
+}
+
+static void change_user(char *user)
+{
+       /* This is the function for which setuid is required, as root */
+       setuid(user_uid(user));
+}
+
+static char *dequote(char *arg)
+{
+       char* narg = NULL;
+
+       if (arg && *arg == '\'') {
+               char* end = arg + strlen(arg) - 1;
+
+               if (end != arg && *end == '\'') {
+                       narg = arg + 1;
+                       *end = '\0';
+               }
+       }
+       return narg;
+}
+
+static char *add_prefix(char *prefix, char* arg)
+{
+       int size;
+
+       if (arg && prefix && strlen(prefix)) {
+               char *n = xmalloc(sizeof(char *) *
+                       (strlen(prefix) + strlen(arg) + 2));
+               strcpy(n, prefix);
+               strcat(n, "/");
+               strcat(n, arg);
+               arg = n;
+       }
+       return arg;
+}
+
+static int check_ssh_interactive(uid_t uid)
+{
+       /* TODO: Check the config file for the user owning uid to see if that
+        * user should be able to execute any commands other than those required
+        * to support repository access.  Return a boolean true/false.
+        */
+       return 1; /* for now */
+}
+
+static int git_check_access(const char *cmd, const char *arg, const char *user)
+{
+       /* TODO: Read some configuration file which maps users and access
+        * to a boolean true/false value.
+        *
+        * The git command can support read and write.
+        * git-receive-pack is ok for readers and writers
+        * git-upload-pack is ok only for writers
+        * git-upload-archive is ok only for writers
+        */
+       return 1; /* assume OK for now */
+}
+
+static int do_git_cmd(const char *cmd, char *arg, char *user)
+{
+       const char *nargv[4];
+       char* narg;
+       int ret;
+
+       if (!(arg = dequote(arg)))
+               die("bad argument");
+       if (strncmp(cmd, "git-", 4))
+               die("bad command");
+
+       change_user(cfg.owner);
+       if (!git_check_access(cmd, arg, user))
+               die("permission denied");
+
+       nargv[0] = cmd;
+       nargv[1] = add_prefix(cfg.git_root, arg);
+       nargv[2] = NULL;
+
+       ret = execvp(nargv[0], (char *const *) nargv);
+       /* Code unreached if execv successful */
+       free(narg);
+       return ret;
+}
+
+static int do_svnserve_cmd(const char *cmd, char *arg, char *user)
+{
+       const char *svnserve_argv[7] = {
+               cmd, "-t", "--root", cfg.svn_root, "--tunnel-user", user, NULL
+       };
+       int ret;
+
+       change_user(cfg.owner);
+       return execvp(svnserve_argv[0], (char *const *) svnserve_argv);
+}
+
+#define SPLIT_CMDLINE_BAD_ENDING 1
+#define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
+static const char *split_cmdline_errors[] = {
+       "cmdline ends with \\",
+       "unclosed quote"
+};
+
+int split_cmdline(char *cmdline, const char ***argv)
+{
+       int src, dst, count = 0, size = 16;
+       char quoted = 0;
+
+       *argv = xmalloc(sizeof(char *) * size);
+
+       /* split alias_string */
+       (*argv)[count++] = cmdline;
+       for (src = dst = 0; cmdline[src];) {
+               char c = cmdline[src];
+               if (!quoted && isspace(c)) {
+                       cmdline[dst++] = 0;
+                       while (cmdline[++src]
+                                       && isspace(cmdline[src]))
+                               ; /* skip */
+                       ALLOC_GROW(*argv, count+1, size);
+                       (*argv)[count++] = cmdline + dst;
+               } else if (!quoted && (c == '\'' || c == '"')) {
+                       quoted = c;
+                       src++;
+               } else if (c == quoted) {
+                       quoted = 0;
+                       src++;
+               } else {
+                       if (c == '\\' && quoted != '\'') {
+                               src++;
+                               c = cmdline[src];
+                               if (!c) {
+                                       free(*argv);
+                                       *argv = NULL;
+                                       return -SPLIT_CMDLINE_BAD_ENDING;
+                               }
+                       }
+                       cmdline[dst++] = c;
+                       src++;
+               }
+       }
+
+       cmdline[dst] = 0;
+
+       if (quoted) {
+               free(*argv);
+               *argv = NULL;
+               return -SPLIT_CMDLINE_UNCLOSED_QUOTE;
+       }
+
+       ALLOC_GROW(*argv, count+1, size);
+       (*argv)[count] = NULL;
+
+       return count;
+}
+
+const char *split_cmdline_strerror(int split_cmdline_errno) {
+       return split_cmdline_errors[-split_cmdline_errno-1];
+}
+
+static void cd_to_homedir(void)
+{
+       const char *home = getenv("HOME");
+       if (!home)
+               die("could not determine user's home directory; HOME is unset");
+       if (chdir(home) == -1)
+               die("could not chdir to user's home directory");
+}
+
+static struct commands {
+       const char *name;
+       int (*exec)(const char *cmd, char *arg, char *user);
+} cmd_list[] = {
+       { "git-receive-pack", do_git_cmd },
+       { "git-upload-pack", do_git_cmd },
+       { "git-upload-archive", do_git_cmd },
+       { "svnserve", do_svnserve_cmd },
+       { NULL },
+};
+
+static int handler(void* user, const char* section, const char* name,
+                   const char* value)
+{
+       cfg_t* pconfig = (cfg_t*)user;
+
+       #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
+       if (MATCH("core", "svn_root"))
+               pconfig->svn_root = xstrdup(value);
+       else if (MATCH("core", "git_root"))
+               pconfig->git_root = xstrdup(value);
+       else if (MATCH("core", "owner"))
+               pconfig->owner = xstrdup(value);
+       else
+               return 0;  /* unknown section/name, error */
+       return 1;
+}
+
+int main(int argc, char **argv)
+{
+       char *prog;
+       const char **user_argv;
+       struct commands *cmd;
+       int devnull_fd;
+       int count;
+
+       /*
+        * Always open file descriptors 0/1/2 to avoid clobbering files
+        * in die().  It also avoids not messing up when the pipes are
+        * dup'ed onto stdin/stdout/stderr in the child processes we spawn.
+        */
+       devnull_fd = open("/dev/null", O_RDWR);
+       while (devnull_fd >= 0 && devnull_fd <= 2)
+               devnull_fd = dup(devnull_fd);
+       if (devnull_fd == -1)
+               die("opening /dev/null failed");
+       close (devnull_fd);
+
+       if (argc < 3)
+               die("invalid arguments");
+
+#ifdef USE_DEFAULTS
+       ini_parse("repo_shell.cfg", handler, &cfg);
+#else
+       if (ini_parse(CFG_FILE, handler, &cfg) < 0)
+               die("cannot read config file %s", CFG_FILE);
+#endif
+
+       prog = xstrdup(argv[2]);
+       if (!strncmp(prog, "git", 3) && isspace(prog[3]))
+               /* Accept "git foo" as if the caller said "git-foo". */
+               prog[3] = '-';
+
+       for (cmd = cmd_list ; cmd->name ; cmd++) {
+               int len = strlen(cmd->name);
+               char *arg;
+               struct passwd *pw;
+               if (strncmp(cmd->name, prog, len))
+                       continue;
+               arg = NULL;
+               switch (prog[len]) {
+               case '\0':
+                       arg = NULL;
+                       break;
+               case ' ':
+                       arg = prog + len + 1;
+                       break;
+               default:
+                       continue;
+               }
+
+               pw = getpwuid(getuid());
+               exit(cmd->exec(cmd->name, arg, pw->pw_name));
+       }
+
+       if (!check_ssh_interactive(getuid()))
+               die("only repository access is allowed");
+
+       cd_to_homedir();
+       count = split_cmdline(prog, &user_argv);
+       if (count >= 0) {
+               execvp(user_argv[0], (char *const *) user_argv);
+               free(user_argv);
+               die("unrecognized command '%s'", argv[2]);
+       } else {
+               free(prog);
+               die("invalid command format '%s': %s", argv[2],
+                   split_cmdline_strerror(count));
+       }
+}