Author: mladen.turk(a)jboss.com
Date: 2008-04-07 02:52:39 -0400 (Mon, 07 Apr 2008)
New Revision: 1521
Modified:
trunk/build/install/xtool/xtool.c
Log:
Add registry tool similar to windows reg but which can operate on 64 bit registry views
Modified: trunk/build/install/xtool/xtool.c
===================================================================
--- trunk/build/install/xtool/xtool.c 2008-04-04 16:22:22 UTC (rev 1520)
+++ trunk/build/install/xtool/xtool.c 2008-04-07 06:52:39 UTC (rev 1521)
@@ -191,12 +191,14 @@
* ---------------------------------------------------------------------
*/
-int utf8_to_unicode_path(wchar_t*, size_t, const char *, size_t);
-int unicode_to_utf8_path(char *, size_t, const wchar_t *);
-int utf8_to_unicode(wchar_t *, size_t, const char *);
-int unicode_to_utf8(char *, size_t, const wchar_t* srcstr);
-int x_wfullpath(wchar_t *, size_t, const char *);
-char *x_forwardslash(char *);
+int utf8_to_unicode_path(wchar_t*, size_t, const char *, size_t);
+int unicode_to_utf8_path(char *, size_t, const wchar_t *);
+int utf8_to_unicode(wchar_t *, size_t, const char *);
+int unicode_to_utf8(char *, size_t, const wchar_t *);
+int x_wfullpath(wchar_t *, size_t, const char *);
+char *x_forwardslash(char *);
+
+
/*
* ---------------------------------------------------------------------
* end of forward declrations
@@ -406,6 +408,21 @@
return p;
}
+static wchar_t *x_wstrdup(const wchar_t *s)
+{
+ wchar_t *p;
+ size_t size;
+ if (s != NULL)
+ size = wcslen(s);
+ else
+ return NULL;
+ p = (wchar_t *)x_malloc((size + 2) * sizeof(wchar_t));
+ memcpy(p, s, size * sizeof(wchar_t));
+ p[size++] = L'\0';
+ p[size] = L'\0';
+ return p;
+}
+
static char *x_strndup(const char *s, size_t size)
{
char *p;
@@ -1557,7 +1574,25 @@
return rv;
}
+static char *expand_wenvars(const wchar_t *str)
+{
+ wchar_t ebuf[INFO_BUFFER_SIZE];
+ char *rv = NULL;
+ DWORD el;
+ el = ExpandEnvironmentStringsW(str, ebuf,
+ INFO_BUFFER_SIZE);
+ if (el > INFO_BUFFER_SIZE) {
+ if (xtrace)
+ warnx("expansion string to large %d", el);
+ }
+ else if (el) {
+ rv = x_strdup_utf8(ebuf);
+ }
+ return rv;
+}
+
+
/* This is the helper code to resolve late bound entry points
* missing from one or more releases of the Win32 API
*/
@@ -2182,6 +2217,41 @@
return 0;
}
+static int utf8_to_unicode(wchar_t* retstr, size_t retlen,
+ const char* srcstr)
+{
+ wchar_t *t = retstr;
+
+ /* leave an extra space for double zero */
+ --retlen;
+ /* This is correct, we don't twist the filename if it is will
+ * definately be shorter than MAX_PATH. It merits some
+ * performance testing to see if this has any effect, but there
+ * seem to be applications that get confused by the resulting
+ * Unicode \\?\ style file names, especially if they use argv[0]
+ * or call the Win32 API functions such as GetModuleName, etc.
+ * Not every application is prepared to handle such names.
+ *
+ * Note that a utf-8 name can never result in more wide chars
+ * than the original number of utf-8 narrow chars.
+ */
+ if (!MultiByteToWideChar(CP_UTF8, 0, srcstr, -1, retstr, retlen))
+ return x_cerror(0);
+ for (; *t; t++) ;
+ *t = L'\0';
+ return 0;
+}
+
+static int unicode_to_utf8(char* retstr, size_t retlen,
+ const wchar_t* srcstr)
+{
+ if (!WideCharToMultiByte(CP_UTF8, 0, srcstr, -1,
+ retstr, retlen, NULL, 0))
+ return x_cerror(0);
+
+ return 0;
+}
+
/*
* An internal function to convert an array of strings (either
* a counted or NULL terminated list, such as an argv[argc] or env[]
@@ -2257,10 +2327,441 @@
/*
* ---------------------------------------------------------------------
- * begin of WIndows GUI
+ * begin of Windows Registry
* ---------------------------------------------------------------------
*/
+#define SAFE_CLOSE_KEY(k) \
+ if ((k) != NULL && (k) != INVALID_HANDLE_VALUE) { \
+ RegCloseKey((k)); \
+ (k) = NULL; \
+ }
+
+typedef struct x_registry_t {
+ HKEY key;
+ REGSAM sam;
+ WCHAR name[256];
+} x_registry_t;
+
+static HKEY reg_rootnamed(const char *name)
+{
+ if (!strnicmp(name, "HKLM", 4))
+ return HKEY_LOCAL_MACHINE;
+ else if (!strnicmp(name, "HKCU", 4))
+ return HKEY_CURRENT_USER;
+ else if (!strnicmp(name, "HKCR", 4))
+ return HKEY_CLASSES_ROOT;
+ else if (!strnicmp(name, "HKCC", 4))
+ return HKEY_CURRENT_CONFIG;
+ else if (!strnicmp(name, "HKU", 3))
+ return HKEY_USERS;
+ else
+ return NULL;
+}
+
+static REGSAM reg_flags(const char *s)
+{
+ REGSAM sam = KEY_QUERY_VALUE;
+
+ if (strchr(s, 'a'))
+ sam |= KEY_ALL_ACCESS;
+ if (strchr(s, 'r'))
+ sam |= KEY_READ;
+ if (strchr(s, 'w'))
+ sam |= KEY_WRITE;
+ if (!(win_osver.dwMajorVersion == 5 &&
+ win_osver.dwMinorVersion == 0)) {
+ if (strstr(s, "32"))
+ sam |= KEY_WOW64_32KEY;
+ else if (strstr(s, "64"))
+ sam |= KEY_WOW64_64KEY;
+ }
+ return sam;
+}
+
+x_registry_t *reg_open(const char *name, const char *sam)
+{
+ int i;
+ HKEY r;
+ char *p;
+ x_registry_t *k;
+
+ if (!(r = reg_rootnamed(name))) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ k = x_malloc(sizeof(x_registry_t));
+ if (!(p = strchr(name, '\\'))) {
+ x_free(k);
+ errno = EINVAL;
+ return NULL;
+ }
+ if ((i = utf8_to_unicode(k->name, 255, p + 1))) {
+ x_free(k);
+ errno = i;
+ return NULL;
+ }
+ k->sam = reg_flags(sam);
+
+ if ((i = RegOpenKeyExW(r, k->name, 0, k->sam, &k->key))
+ != ERROR_SUCCESS) {
+ i = x_cerror(i);
+ x_free(k);
+ errno = i;
+ return NULL;
+ }
+
+ return k;
+}
+
+static int reg_delete(const char *name, const char *sam, int all, const char *value)
+{
+ int i = ERROR_SUCCESS;
+ HKEY r;
+ char *p;
+ x_registry_t k;
+ wchar_t v[256];
+ wchar_t *s = NULL;
+
+ if (!(r = reg_rootnamed(name))) {
+ errno = EINVAL;
+ return errno;
+ }
+
+ if (!(p = strchr(name, '\\'))) {
+ errno = EINVAL;
+ return errno;
+ }
+ if ((i = utf8_to_unicode(k.name, 255, p + 1))) {
+ errno = i;
+ return errno;
+ }
+ k.sam = reg_flags(sam);
+
+ if ((s = wcsrchr(k.name, L'\\'))) {
+ *(s++) = L'\0';
+ }
+ else {
+ errno = EINVAL;
+ return errno;
+ }
+ if ((i = RegOpenKeyExW(r, k.name, 0, k.sam, &k.key))
+ != ERROR_SUCCESS) {
+ errno = x_cerror(i);
+ return errno;
+ }
+ if (value) {
+ i = SHDeleteValueW(k.key, s, v);
+ }
+ else {
+ if (all)
+ i = SHDeleteKeyW(k.key, s);
+ else
+ i = SHDeleteEmptyKeyW(k.key, s);
+ }
+ SAFE_CLOSE_KEY(k.key);
+ return i;
+}
+
+x_registry_t *reg_create(const char *name, const char *sam)
+{
+ DWORD c;
+ int i;
+ HKEY r;
+ char *p;
+ x_registry_t *k;
+
+ if (!(r = reg_rootnamed(name))) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ k = x_malloc(sizeof(x_registry_t));
+ if (!(p = strchr(name, '\\'))) {
+ x_free(k);
+ errno = EINVAL;
+ return NULL;
+ }
+ if ((i = utf8_to_unicode(k->name, 255, p + 1))) {
+ x_free(k);
+ errno = i;
+ return NULL;
+ }
+ k->sam = reg_flags(sam);
+
+ if ((i = RegCreateKeyExW(r, k->name, 0, NULL, 0,
+ k->sam, NULL, &k->key, &c)) != ERROR_SUCCESS) {
+ i = x_cerror(i);
+ x_free(k);
+ errno = i;
+ return NULL;
+ }
+
+ return k;
+}
+
+
+static void reg_close(x_registry_t *key)
+{
+ if (key) {
+ SAFE_CLOSE_KEY(key->key);
+ x_free(key);
+ }
+}
+
+static int reg_type(x_registry_t *k, const char *name)
+{
+ int rc;
+ int rt;
+ wchar_t *wn;
+
+ if (k && IS_INVALID_HANDLE(k->key)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!(wn = x_wstrdup_utf8(name))) {
+ errno = EINVAL;
+ return -1;
+ }
+ if ((rc = (LONG)RegQueryValueExW(k->key, wn, NULL,
+ &rt, NULL, NULL)) != ERROR_SUCCESS) {
+ errno = rc;
+ rt = -1;
+ }
+ x_free(wn);
+ return rt;
+}
+
+static int reg_size(x_registry_t *k, const char *name)
+{
+ int rc;
+ int rt;
+ wchar_t *wn;
+
+ if (k && IS_INVALID_HANDLE(k->key)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!(wn = x_wstrdup_utf8(name))) {
+ errno = EINVAL;
+ return -1;
+ }
+ if ((rc = (LONG)RegQueryValueExW(k->key, wn, NULL,
+ NULL, NULL, &rt)) != ERROR_SUCCESS) {
+ rt = -1;
+ }
+ x_free(wn);
+ return rt;
+}
+
+static const char *reg_stype(int type)
+{
+ switch (type) {
+ case REG_BINARY:
+ return "REG_BINARY";
+ case REG_DWORD:
+ return "REG_DWORD";
+ case REG_EXPAND_SZ:
+ return "REG_EXPAND_SZ";
+ case REG_MULTI_SZ:
+ return "REG_MULTI_SZ";
+ case REG_QWORD:
+ return "REG_QWORD";
+ case REG_SZ:
+ return "REG_SZ";
+ case REG_DWORD_BIG_ENDIAN:
+ return "REG_DWORD_BIG_ENDIAN";
+ break;
+ }
+ return "UNKNOWN";
+}
+
+static int reg_ntype(const char *type)
+{
+ if (!stricmp(type, "REG_BINARY"))
+ return REG_BINARY;
+ else if (!stricmp(type, "REG_DWORD"))
+ return REG_DWORD;
+ else if (!stricmp(type, "REG_EXPAND_SZ"))
+ return REG_EXPAND_SZ;
+ else if (!stricmp(type, "REG_MULTI_SZ"))
+ return REG_MULTI_SZ;
+ else if (!stricmp(type, "REG_QWORD"))
+ return REG_QWORD;
+ else if (!stricmp(type, "REG_SZ"))
+ return REG_SZ;
+ else {
+ errno = EINVAL;
+ return REG_NONE;
+ }
+}
+
+
+static char *reg_value(x_registry_t *k, const char *name, int sc)
+{
+ int rc = 0;
+ DWORD rt;
+ DWORD rl, i;
+ INT64 qw;
+ wchar_t *wn;
+ char tbuf[128];
+ unsigned char *buff = NULL;
+ char *value = NULL;
+ wchar_t *wp;
+ char *cp;
+
+ if (k && IS_INVALID_HANDLE(k->key)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (!(wn = x_wstrdup_utf8(name))) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if ((rc = (LONG)RegQueryValueExW(k->key, wn, NULL,
+ &rt, NULL, &rl)) != ERROR_SUCCESS) {
+ goto cleanup;
+ }
+ buff = x_malloc((size_t)rl);
+ if ((rc = (LONG)RegQueryValueExW(k->key, wn, NULL,
+ &rt, buff, &rl)) != ERROR_SUCCESS) {
+ goto cleanup;
+ }
+ switch (rt) {
+ case REG_SZ:
+ value = x_strdup_utf8((wchar_t *)buff);
+ break;
+ case REG_MULTI_SZ:
+ for (wp = (wchar_t *)buff; *wp; wp++) {
+ while (*wp)
+ wp++;
+ if (*(wp + 1) != L'\0')
+ *wp = sc;
+ }
+ value = x_strdup_utf8((wchar_t *)buff);
+ break;
+ case REG_EXPAND_SZ:
+ value = expand_wenvars((wchar_t *)buff);
+ break;
+ case REG_DWORD:
+ memcpy(&rt, buff, 4);
+ value = x_strdup(itoa(rt, tbuf, 10));
+ break;
+ case REG_QWORD:
+ memcpy(&qw, buff, 8);
+ value = x_strdup(_i64toa(qw, tbuf, 10));
+ break;
+ case REG_BINARY:
+ value = x_malloc(rl * 4 + 1);
+ for (i = 0, cp = value; i < (rl - 1); i++) {
+ sprintf(cp, "%02x, ", buff[i]);
+ cp += 4;
+ }
+ sprintf(cp, "%02x", buff[i]);
+ break;
+ default:
+ rc = EBADF;
+ break;
+ }
+
+cleanup:
+ x_free(wn);
+ x_free(buff);
+ errno = rc;
+ return value;
+}
+
+static int reg_set(x_registry_t *k, const char *name,
+ int type, const char *value, int sc)
+{
+ int rc = 0;
+ DWORD rt, st = type;
+ DWORD rl, i;
+ INT64 qw;
+ wchar_t *wn;
+ unsigned char *buff = NULL;
+ wchar_t *wp, *p;
+
+ if (k && IS_INVALID_HANDLE(k->key)) {
+ errno = EINVAL;
+ return errno;
+ }
+ if (!(wn = x_wstrdup_utf8(name))) {
+ errno = EINVAL;
+ return errno;
+ }
+ if ((rc = (LONG)RegQueryValueExW(k->key, wn, NULL,
+ &rt, NULL, &rl)) == ERROR_SUCCESS) {
+ if (st != REG_NONE && st != rt) {
+ rc = EINVAL;
+ goto cleanup;
+ }
+ st = rt;
+ }
+ if (st == REG_NONE)
+ st = REG_SZ;
+
+ switch (st) {
+ case REG_SZ:
+ case REG_EXPAND_SZ:
+ wp = x_wstrdup_utf8(value);
+ rc = RegSetValueExW(k->key, wn, 0, st,
+ (const unsigned char *)wp,
+ (wcslen(wp) + 1) * sizeof(wchar_t));
+ x_free(wp);
+ break;
+ case REG_MULTI_SZ:
+ wp = x_wstrdup_utf8(value);
+ rl = wcslen(wp);
+ for (p = wp; *p; p++) {
+ if (*p == sc)
+ *p = L'\0';
+ }
+ rc = RegSetValueExW(k->key, wn, 0, st,
+ (const unsigned char *)wp,
+ (rl + 2) * sizeof(wchar_t));
+ x_free(wp);
+ break;
+ case REG_DWORD:
+ i = atoi(value);
+ rc = RegSetValueExW(k->key, wn, 0, st,
+ (const unsigned char *)&i, 4);
+ break;
+ case REG_QWORD:
+ qw = _atoi64(value);
+ rc = RegSetValueExW(k->key, wn, 0, st,
+ (const unsigned char *)&qw, 8);
+ break;
+ case REG_BINARY:
+
+ break;
+ default:
+ rc = EBADF;
+ break;
+ }
+
+cleanup:
+ x_free(wn);
+ x_free(buff);
+ errno = rc;
+ return errno;
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * end of Windows Registry
+ * ---------------------------------------------------------------------
+ */
+
+/*
+ * ---------------------------------------------------------------------
+ * begin of Windows GUI
+ * ---------------------------------------------------------------------
+ */
+
#define MGUI_WINDOWS 32
HICON gui_h16Icon = NULL;
@@ -2740,7 +3241,7 @@
/*
* ---------------------------------------------------------------------
- * end of WIndows GUI
+ * end of Windows GUI
* ---------------------------------------------------------------------
*/
@@ -2969,6 +3470,37 @@
return retval;
}
+static int prog_reg_usage(int retval)
+{
+ fprintf(stderr, "Usage: %s [OPTION]... OPERATION KEY [VALUE]\n",
progname);
+ fprintf(stderr, "Registry tool\n\n");
+ fprintf(stderr, " OPERATION\n");
+ fprintf(stderr, " query VALUE Query Registry Key Value\n");
+ fprintf(stderr, " add [VALUE] Add Registry Key\n");
+ fprintf(stderr, " del [VALUE] Delete Registry Key or Value\n");
+ fprintf(stderr, " type VALUE Query Registry Key Value type\n");
+ fprintf(stderr, " size VALUE Query Registry Key Value size\n");
+ fprintf(stderr, " enum VALUE Enumerate Registy Key\n");
+ fprintf(stderr, " VALUE can be 'key', 'value'
or 'all'\n\n");
+ fprintf(stderr, " -a Delete all keys and subkeys.\n");
+ fprintf(stderr, " -m STRING Registry access mode.\n");
+ fprintf(stderr, " a all access\n");
+ fprintf(stderr, " r read access\n");
+ fprintf(stderr, " w wite access\n");
+ fprintf(stderr, " 32 operate on the 32-bit registry
view\n");
+ fprintf(stderr, " 64 operate on the 64-bit registry
view\n");
+ fprintf(stderr, " -t STRING Registry type.\n");
+ fprintf(stderr, " REG_SZ\n");
+ fprintf(stderr, " REG_EXPAND_SZ\n");
+ fprintf(stderr, " REG_MULTI_SZ\n");
+ fprintf(stderr, " REG_DWORD\n");
+ fprintf(stderr, " REG_QWORD\n");
+ fprintf(stderr, " -f CHAR Field separator for REG_MULTI_SZ.\n");
+ fprintf(stderr, " -d STRING Data to be used for add operation.\n");
+ print_stdusage();
+ return retval;
+}
+
/*
* ---------------------------------------------------------------------
* end of programs usage
@@ -4243,6 +4775,200 @@
return rv;
}
+static int prog_reg(int argc, const char **argv, const char **env)
+{
+ int ch, rv = 0;
+ int delall = 0;
+ int sc = ',';
+ char *rm = NULL;
+ char *rs = NULL;
+ char *rd = NULL;
+ int rt = REG_NONE;
+ x_registry_t *reg = NULL;
+
+ while ((ch = getopt(argc, argv, "ad:f:m:r:t:hqvV", 1)) != EOF) {
+ switch (ch) {
+ case '.':
+ if (!stricmp(optarg, "verbose"))
+ xtrace = 1;
+ else if (!stricmp(optarg, "version"))
+ return print_banner(1);
+ else if (!stricmp(optarg, "help"))
+ return prog_reg_usage(0);
+ else
+ return prog_reg_usage(EINVAL);
+ break;
+ case 'a':
+ delall = 1;
+ break;
+ case 'm':
+ rm = x_strdup(optarg);
+ break;
+ case 'f':
+ sc = *optarg;
+ break;
+ case 'd':
+ rd = x_strdup(optarg);
+ break;
+ case 't':
+ rt = reg_ntype(optarg);
+ if (rt == REG_NONE) {
+ return prog_reg_usage(EINVAL);
+ }
+ break;
+ case 'v':
+ xtrace = 1;
+ break;
+ case 'V':
+ xtrace = 9;
+ break;
+ case 'q':
+ xquiet = 1;
+ break;
+ case 'h':
+ return prog_reg_usage(0);
+ break;
+ case '?':
+ case ':':
+ return EINVAL;
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 2) {
+ return prog_reg_usage(EINVAL);
+ }
+ if (!stricmp(argv[0], "query")) {
+ if (argc < 3) {
+ return prog_reg_usage(EINVAL);
+ }
+ if (!(reg = reg_open(argv[1], rm ? rm : "r"))) {
+ rv = x_perror(0, argv[1]);
+ goto cleanup;
+ }
+ if (!(rs = reg_value(reg, argv[2], sc))) {
+ rv = x_perror(0, argv[2]);
+ goto cleanup;
+ }
+ }
+ if (!stricmp(argv[0], "add")) {
+ if (argc < 2) {
+ return prog_reg_usage(EINVAL);
+ }
+ if (!(reg = reg_create(argv[1], rm ? rm : "rw"))) {
+ rv = x_perror(0, argv[1]);
+ goto cleanup;
+ }
+ if (rd) {
+ if ((rv = reg_set(reg, argv[2], rt, rd, sc)))
+ x_perror(rv, argv[2]);
+ }
+ }
+ if (!stricmp(argv[0], "del")) {
+ const char *v = NULL;
+ if (argc < 2)
+ return prog_reg_usage(EINVAL);
+ else if (argc > 2)
+ v = argv[2];
+ rv = reg_delete(argv[1], rm ? rm : "rw",
+ delall, v);
+ }
+ if (!stricmp(argv[0], "type")) {
+ if (argc < 3) {
+ return prog_reg_usage(EINVAL);
+ }
+ if (!(reg = reg_open(argv[1], rm ? rm : "r"))) {
+ rv = x_perror(0, argv[1]);
+ goto cleanup;
+ }
+ if ((rv = reg_type(reg, argv[2])) >= 0) {
+ fputs(reg_stype(rv), stdout);
+ rv = 0;
+ }
+ }
+ if (!stricmp(argv[0], "size")) {
+ if (argc < 3) {
+ return prog_reg_usage(EINVAL);
+ }
+ if (!(reg = reg_open(argv[1], rm ? rm : "r"))) {
+ rv = x_perror(0, argv[1]);
+ goto cleanup;
+ }
+ rv = reg_size(reg, argv[2]);
+ fprintf(stdout, "%d", rv);
+ }
+ if (!stricmp(argv[0], "enum")) {
+ DWORD idx, nl;
+ int all = 0;
+ wchar_t nb[256];
+ if (argc < 3) {
+ return prog_reg_usage(EINVAL);
+ }
+ if (!(reg = reg_open(argv[1], rm ? rm : "r"))) {
+ rv = x_perror(0, argv[1]);
+ goto cleanup;
+ }
+ idx = 0;
+ if (!stricmp(argv[2], "all"))
+ all = 1;
+ if (all || !stricmp(argv[2], "key")) {
+ nl = 256;
+ while ((rv = RegEnumKeyExW(reg->key,
+ idx,
+ nb,
+ &nl,
+ NULL,
+ NULL,
+ NULL,
+ NULL)) == ERROR_SUCCESS) {
+ if (idx++)
+ fputc('\n', stdout);
+ if (all)
+ fputc('\\', stdout);
+ fprintf(stdout, "%S", nb);
+ idx++;
+ nl = 256;
+ }
+ if (rv != ERROR_NO_MORE_ITEMS)
+ goto cleanup;
+ }
+ if (all || !strnicmp(argv[2], "val", 3)) {
+ nl = 256;
+ if (idx++)
+ fputc('\n', stdout);
+ idx = 0;
+ while ((rv = RegEnumValueW(reg->key,
+ idx,
+ nb,
+ &nl,
+ NULL,
+ NULL,
+ NULL,
+ NULL)) == ERROR_SUCCESS) {
+ if (idx++)
+ fputc('\n', stdout);
+ fprintf(stdout, "%S", nb);
+ idx++;
+ nl = 256;
+ }
+ if (rv != ERROR_NO_MORE_ITEMS)
+ goto cleanup;
+ }
+ rv = 0;
+ }
+
+cleanup:
+ if (rs) {
+ fputs(rs, stdout);
+ }
+ x_free(rs);
+ x_free(rm);
+ x_free(rd);
+ reg_close(reg);
+ return rv;
+}
+
/*
* ---------------------------------------------------------------------
* end of programs
@@ -4265,6 +4991,7 @@
{ "image", prog_image },
{ "coff", prog_image },
{ "html", prog_html },
+ { "reg", prog_reg },
{ NULL, NULL }
};