From 2f461da2a0ac8a02acd056e365a2ab2721f54b07 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 19 Dec 2013 16:59:46 +0100 Subject: [PATCH] install: install all PATH binaries found This should fix the issues with symlinks in /bin pointing to /usr/bin on some distributions. --- Makefile | 2 + install/dracut-install.c | 88 +++--- install/strv.c | 587 +++++++++++++++++++++++++++++++++++++++ install/strv.h | 118 ++++++++ install/util.c | 235 ++++++++++++++++ install/util.h | 22 +- 6 files changed, 1007 insertions(+), 45 deletions(-) create mode 100644 install/strv.c create mode 100644 install/strv.h diff --git a/Makefile b/Makefile index ba551cfa..fed5f48b 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,7 @@ DRACUT_INSTALL_OBJECTS = \ install/dracut-install.o \ install/hashmap.o\ install/log.o \ + install/strv.o \ install/util.o # deps generated with gcc -MM @@ -56,6 +57,7 @@ install/hashmap.o: install/hashmap.c install/util.h install/macro.h install/log. install/hashmap.h install/log.o: install/log.c install/log.h install/macro.h install/util.h install/util.o: install/util.c install/util.h install/macro.h install/log.h +install/strv.o: install/strv.c install/strv.h install/util.h install/macro.h install/log.h install/dracut-install: $(DRACUT_INSTALL_OBJECTS) diff --git a/install/dracut-install.c b/install/dracut-install.c index 6b9c613b..397aa621 100644 --- a/install/dracut-install.c +++ b/install/dracut-install.c @@ -44,6 +44,7 @@ #include "log.h" #include "hashmap.h" #include "util.h" +#include "strv.h" static bool arg_hmac = false; static bool arg_createdir = false; @@ -784,13 +785,13 @@ static int resolve_lazy(int argc, char **argv) return ret; } -static char *find_binary(const char *src) +static char **find_binary(const char *src) { - _cleanup_free_ char *path = NULL; - char *p, *q; - bool end = false; + char *path = NULL; + _cleanup_strv_free_ char **p = NULL; + char **ret = NULL; + char **q; char *newsrc = NULL; - int ret; path = getenv("PATH"); @@ -798,34 +799,21 @@ static char *find_binary(const char *src) log_error("PATH is not set"); exit(EXIT_FAILURE); } - path = strdup(path); - p = path; - - if (path == NULL) { - log_error("Out of memory!"); - exit(EXIT_FAILURE); - } log_debug("PATH=%s", path); - do { + p = strv_split(path, ":"); + + STRV_FOREACH(q, p) { struct stat sb; + int r; - for (q = p; *q && *q != ':'; q++) ; - - if (*q == '\0') - end = true; - else - *q = '\0'; - - ret = asprintf(&newsrc, "%s/%s", p, src); - if (ret < 0) { + r = asprintf(&newsrc, "%s/%s", *q, src); + if (r < 0) { log_error("Out of memory!"); exit(EXIT_FAILURE); } - p = q + 1; - if (stat(newsrc, &sb) != 0) { log_debug("stat(%s) != 0", newsrc); free(newsrc); @@ -833,30 +821,37 @@ static char *find_binary(const char *src) continue; } - end = true; + strv_push(&ret, newsrc); - } while (!end); + }; - if (newsrc) - log_debug("find_binary(%s) == %s", src, newsrc); + if (ret) { + STRV_FOREACH(q, ret) { + log_debug("find_binary(%s) == %s", src, *q); + } + } - return newsrc; + return ret; } static int install_one(const char *src, const char *dst) { int r = EXIT_SUCCESS; - int ret; + int ret = 0; if (strchr(src, '/') == NULL) { - char *newsrc = find_binary(src); - if (newsrc) { - log_debug("dracut_install '%s' '%s'", newsrc, dst); - ret = dracut_install(newsrc, dst, arg_createdir, arg_resolvedeps, true); - if (ret == 0) { - log_debug("dracut_install '%s' '%s' OK", newsrc, dst); + char **q = NULL; + char **p = find_binary(src); + if (p) { + STRV_FOREACH(q, p) { + char *newsrc = *q; + log_debug("dracut_install '%s' '%s'", newsrc, dst); + ret = dracut_install(newsrc, dst, arg_createdir, arg_resolvedeps, true); + if (ret == 0) { + log_debug("dracut_install '%s' '%s' OK", newsrc, dst); + } } - free(newsrc); + strv_free(p); } else { ret = -1; } @@ -877,17 +872,22 @@ static int install_all(int argc, char **argv) int r = EXIT_SUCCESS; int i; for (i = 0; i < argc; i++) { - int ret; + int ret = 0; log_debug("Handle '%s'", argv[i]); if (strchr(argv[i], '/') == NULL) { - _cleanup_free_ char *newsrc = find_binary(argv[i]); - if (newsrc) { - log_debug("dracut_install '%s'", newsrc); - ret = dracut_install(newsrc, newsrc, arg_createdir, arg_resolvedeps, true); - if (ret == 0) { - log_debug("dracut_install '%s' OK", newsrc); + char **q = NULL; + char **p = find_binary(argv[i]); + if (p) { + STRV_FOREACH(q, p) { + char *newsrc = *q; + log_debug("dracut_install '%s'", newsrc); + ret = dracut_install(newsrc, newsrc, arg_createdir, arg_resolvedeps, true); + if (ret == 0) { + log_debug("dracut_install '%s' OK", newsrc); + } } + strv_free(p); } else { ret = -1; } diff --git a/install/strv.c b/install/strv.c new file mode 100644 index 00000000..152b70c1 --- /dev/null +++ b/install/strv.c @@ -0,0 +1,587 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "util.h" +#include "strv.h" + +char *strv_find(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (streq(*i, name)) + return *i; + + return NULL; +} + +char *strv_find_prefix(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (startswith(*i, name)) + return *i; + + return NULL; +} + +void strv_free(char **l) { + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + + free(l); +} + +char **strv_copy(char * const *l) { + char **r, **k; + + k = r = new(char*, strv_length(l) + 1); + if (!r) + return NULL; + + if (l) + for (; *l; k++, l++) { + *k = strdup(*l); + if (!*k) { + strv_free(r); + return NULL; + } + } + + *k = NULL; + return r; +} + +unsigned strv_length(char * const *l) { + unsigned n = 0; + + if (!l) + return 0; + + for (; *l; l++) + n++; + + return n; +} + +char **strv_new_ap(const char *x, va_list ap) { + const char *s; + char **a; + unsigned n = 0, i = 0; + va_list aq; + + /* As a special trick we ignore all listed strings that equal + * (const char*) -1. This is supposed to be used with the + * STRV_IFNOTNULL() macro to include possibly NULL strings in + * the string list. */ + + if (x) { + n = x == (const char*) -1 ? 0 : 1; + + va_copy(aq, ap); + while ((s = va_arg(aq, const char*))) { + if (s == (const char*) -1) + continue; + + n++; + } + + va_end(aq); + } + + a = new(char*, n+1); + if (!a) + return NULL; + + if (x) { + if (x != (const char*) -1) { + a[i] = strdup(x); + if (!a[i]) + goto fail; + i++; + } + + while ((s = va_arg(ap, const char*))) { + + if (s == (const char*) -1) + continue; + + a[i] = strdup(s); + if (!a[i]) + goto fail; + + i++; + } + } + + a[i] = NULL; + + return a; + +fail: + strv_free(a); + return NULL; +} + +char **strv_new(const char *x, ...) { + char **r; + va_list ap; + + va_start(ap, x); + r = strv_new_ap(x, ap); + va_end(ap); + + return r; +} + +char **strv_merge(char **a, char **b) { + char **r, **k; + + if (!a) + return strv_copy(b); + + if (!b) + return strv_copy(a); + + r = new(char*, strv_length(a) + strv_length(b) + 1); + if (!r) + return NULL; + + for (k = r; *a; k++, a++) { + *k = strdup(*a); + if (!*k) + goto fail; + } + + for (; *b; k++, b++) { + *k = strdup(*b); + if (!*k) + goto fail; + } + + *k = NULL; + return r; + +fail: + strv_free(r); + return NULL; +} + +char **strv_merge_concat(char **a, char **b, const char *suffix) { + char **r, **k; + + /* Like strv_merge(), but appends suffix to all strings in b, before adding */ + + if (!b) + return strv_copy(a); + + r = new(char*, strv_length(a) + strv_length(b) + 1); + if (!r) + return NULL; + + k = r; + if (a) + for (; *a; k++, a++) { + *k = strdup(*a); + if (!*k) + goto fail; + } + + for (; *b; k++, b++) { + *k = strappend(*b, suffix); + if (!*k) + goto fail; + } + + *k = NULL; + return r; + +fail: + strv_free(r); + return NULL; + +} + +char **strv_split(const char *s, const char *separator) { + char *state; + char *w; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_SEPARATOR(w, l, s, separator, state) + n++; + + r = new(char*, n+1); + if (!r) + return NULL; + + i = 0; + FOREACH_WORD_SEPARATOR(w, l, s, separator, state) { + r[i] = strndup(w, l); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + } + + r[i] = NULL; + return r; +} + +char **strv_split_quoted(const char *s) { + char *state; + char *w; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_QUOTED(w, l, s, state) + n++; + + r = new(char*, n+1); + if (!r) + return NULL; + + i = 0; + FOREACH_WORD_QUOTED(w, l, s, state) { + r[i] = cunescape_length(w, l); + if (!r[i]) { + strv_free(r); + return NULL; + } + i++; + } + + r[i] = NULL; + return r; +} + +char **strv_split_newlines(const char *s) { + char **l; + unsigned n; + + assert(s); + + /* Special version of strv_split() that splits on newlines and + * suppresses an empty string at the end */ + + l = strv_split(s, NEWLINE); + if (!l) + return NULL; + + n = strv_length(l); + if (n <= 0) + return l; + + if (isempty(l[n-1])) { + free(l[n-1]); + l[n-1] = NULL; + } + + return l; +} + +char *strv_join(char **l, const char *separator) { + char *r, *e; + char **s; + size_t n, k; + + if (!separator) + separator = " "; + + k = strlen(separator); + + n = 0; + STRV_FOREACH(s, l) { + if (n != 0) + n += k; + n += strlen(*s); + } + + r = new(char, n+1); + if (!r) + return NULL; + + e = r; + STRV_FOREACH(s, l) { + if (e != r) + e = stpcpy(e, separator); + + e = stpcpy(e, *s); + } + + *e = 0; + + return r; +} + +char **strv_append(char **l, const char *s) { + char **r, **k; + + if (!l) + return strv_new(s, NULL); + + if (!s) + return strv_copy(l); + + r = new(char*, strv_length(l)+2); + if (!r) + return NULL; + + for (k = r; *l; k++, l++) { + *k = strdup(*l); + if (!*k) + goto fail; + } + + k[0] = strdup(s); + if (!k[0]) + goto fail; + + k[1] = NULL; + return r; + +fail: + strv_free(r); + return NULL; +} + +int strv_push(char ***l, char *value) { + char **c; + unsigned n; + + if (!value) + return 0; + + n = strv_length(*l); + c = realloc(*l, sizeof(char*) * (n + 2)); + if (!c) + return -ENOMEM; + + c[n] = value; + c[n+1] = NULL; + + *l = c; + return 0; +} + +int strv_extend(char ***l, const char *value) { + char *v; + int r; + + if (!value) + return 0; + + v = strdup(value); + if (!v) + return -ENOMEM; + + r = strv_push(l, v); + if (r < 0) + free(v); + + return r; +} + +char **strv_uniq(char **l) { + char **i; + + /* Drops duplicate entries. The first identical string will be + * kept, the others dropped */ + + STRV_FOREACH(i, l) + strv_remove(i+1, *i); + + return l; +} + +char **strv_remove(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of s in the string list, edits + * in-place. */ + + for (f = t = l; *f; f++) { + + if (streq(*f, s)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_remove_prefix(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of a string prefixed with s in the + * string list, edits in-place. */ + + for (f = t = l; *f; f++) { + + if (startswith(*f, s)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_parse_nulstr(const char *s, size_t l) { + const char *p; + unsigned c = 0, i = 0; + char **v; + + assert(s || l <= 0); + + if (l <= 0) + return new0(char*, 1); + + for (p = s; p < s + l; p++) + if (*p == 0) + c++; + + if (s[l-1] != 0) + c++; + + v = new0(char*, c+1); + if (!v) + return NULL; + + p = s; + while (p < s + l) { + const char *e; + + e = memchr(p, 0, s + l - p); + + v[i] = strndup(p, e ? e - p : s + l - p); + if (!v[i]) { + strv_free(v); + return NULL; + } + + i++; + + if (!e) + break; + + p = e + 1; + } + + assert(i == c); + + return v; +} + +char **strv_split_nulstr(const char *s) { + const char *i; + char **r = NULL; + + NULSTR_FOREACH(i, s) + if (strv_extend(&r, i) < 0) { + strv_free(r); + return NULL; + } + + if (!r) + return strv_new(NULL, NULL); + + return r; +} + +bool strv_overlap(char **a, char **b) { + char **i, **j; + + STRV_FOREACH(i, a) { + STRV_FOREACH(j, b) { + if (streq(*i, *j)) + return true; + } + } + + return false; +} + +static int str_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + + return strcmp(*a, *b); +} + +char **strv_sort(char **l) { + + if (strv_isempty(l)) + return l; + + qsort(l, strv_length(l), sizeof(char*), str_compare); + return l; +} + +void strv_print(char **l) { + char **s; + + if (!l) + return; + + STRV_FOREACH(s, l) + puts(*s); +} diff --git a/install/strv.h b/install/strv.h new file mode 100644 index 00000000..193f1dd1 --- /dev/null +++ b/install/strv.h @@ -0,0 +1,118 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "util.h" + +char *strv_find(char **l, const char *name) _pure_; +char *strv_find_prefix(char **l, const char *name) _pure_; + +void strv_free(char **l); +DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free); +#define _cleanup_strv_free_ _cleanup_(strv_freep) + +char **strv_copy(char * const *l); +unsigned strv_length(char * const *l) _pure_; + +char **strv_merge(char **a, char **b); +char **strv_merge_concat(char **a, char **b, const char *suffix); +char **strv_append(char **l, const char *s); +int strv_extend(char ***l, const char *value); +int strv_push(char ***l, char *value); + +char **strv_remove(char **l, const char *s); +char **strv_remove_prefix(char **l, const char *s); +char **strv_uniq(char **l); + +#define strv_contains(l, s) (!!strv_find((l), (s))) + +char **strv_new(const char *x, ...) _sentinel_; +char **strv_new_ap(const char *x, va_list ap); + +static inline const char* STRV_IFNOTNULL(const char *x) { + return x ? x : (const char *) -1; +} + +static inline bool strv_isempty(char * const *l) { + return !l || !*l; +} + +char **strv_split(const char *s, const char *separator); +char **strv_split_quoted(const char *s); +char **strv_split_newlines(const char *s); + +char *strv_join(char **l, const char *separator); + +char **strv_parse_nulstr(const char *s, size_t l); +char **strv_split_nulstr(const char *s); + +bool strv_overlap(char **a, char **b) _pure_; + +#define STRV_FOREACH(s, l) \ + for ((s) = (l); (s) && *(s); (s)++) + +#define STRV_FOREACH_BACKWARDS(s, l) \ + STRV_FOREACH(s, l) \ + ; \ + for ((s)--; (l) && ((s) >= (l)); (s)--) + +#define STRV_FOREACH_PAIR(x, y, l) \ + for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1)) + +char **strv_sort(char **l); +void strv_print(char **l); + +#define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL })) + +#define STRV_MAKE_EMPTY ((char*[1]) { NULL }) + +#define strv_from_stdarg_alloca(first) \ + ({ \ + char **_l; \ + \ + if (!first) \ + _l = (char**) &first; \ + else { \ + unsigned _n; \ + va_list _ap; \ + \ + _n = 1; \ + va_start(_ap, first); \ + while (va_arg(_ap, char*)) \ + _n++; \ + va_end(_ap); \ + \ + _l = newa(char*, _n+1); \ + _l[_n = 0] = (char*) first; \ + va_start(_ap, first); \ + for (;;) { \ + _l[++_n] = va_arg(_ap, char*); \ + if (!_l[_n]) \ + break; \ + } \ + va_end(_ap); \ + } \ + _l; \ + }) diff --git a/install/util.c b/install/util.c index 9aa131f8..c28db764 100644 --- a/install/util.c +++ b/install/util.c @@ -277,3 +277,238 @@ char *strjoin(const char *x, ...) { return r; } + +char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) { + char *r, *t; + const char *f; + size_t pl; + + assert(s); + + /* Undoes C style string escaping, and optionally prefixes it. */ + + pl = prefix ? strlen(prefix) : 0; + + r = new(char, pl+length+1); + if (!r) + return r; + + if (prefix) + memcpy(r, prefix, pl); + + for (f = s, t = r + pl; f < s + length; f++) { + + if (*f != '\\') { + *(t++) = *f; + continue; + } + + f++; + + switch (*f) { + + case 'a': + *(t++) = '\a'; + break; + case 'b': + *(t++) = '\b'; + break; + case 'f': + *(t++) = '\f'; + break; + case 'n': + *(t++) = '\n'; + break; + case 'r': + *(t++) = '\r'; + break; + case 't': + *(t++) = '\t'; + break; + case 'v': + *(t++) = '\v'; + break; + case '\\': + *(t++) = '\\'; + break; + case '"': + *(t++) = '"'; + break; + case '\'': + *(t++) = '\''; + break; + + case 's': + /* This is an extension of the XDG syntax files */ + *(t++) = ' '; + break; + + case 'x': { + /* hexadecimal encoding */ + int a, b; + + a = unhexchar(f[1]); + b = unhexchar(f[2]); + + if (a < 0 || b < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = 'x'; + } else { + *(t++) = (char) ((a << 4) | b); + f += 2; + } + + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + /* octal encoding */ + int a, b, c; + + a = unoctchar(f[0]); + b = unoctchar(f[1]); + c = unoctchar(f[2]); + + if (a < 0 || b < 0 || c < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = f[0]; + } else { + *(t++) = (char) ((a << 6) | (b << 3) | c); + f += 2; + } + + break; + } + + case 0: + /* premature end of string.*/ + *(t++) = '\\'; + goto finish; + + default: + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = *f; + break; + } + } + +finish: + *t = 0; + return r; +} + +char *cunescape_length(const char *s, size_t length) { + return cunescape_length_with_prefix(s, length, NULL); +} + + +/* Split a string into words, but consider strings enclosed in '' and + * "" as words even if they include spaces. */ +char *split_quoted(const char *c, size_t *l, char **state) { + const char *current, *e; + bool escaped = false; + + assert(c); + assert(l); + assert(state); + + current = *state ? *state : c; + + current += strspn(current, WHITESPACE); + + if (*current == 0) + return NULL; + + else if (*current == '\'') { + current ++; + + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (*e == '\'') + break; + } + + *l = e-current; + *state = (char*) (*e == 0 ? e : e+1); + + } else if (*current == '\"') { + current ++; + + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (*e == '\"') + break; + } + + *l = e-current; + *state = (char*) (*e == 0 ? e : e+1); + + } else { + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (strchr(WHITESPACE, *e)) + break; + } + *l = e-current; + *state = (char*) e; + } + + return (char*) current; +} + +/* Split a string into words. */ +char *split(const char *c, size_t *l, const char *separator, char **state) { + char *current; + + current = *state ? *state : (char*) c; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, separator); + *l = strcspn(current, separator); + *state = current+*l; + + return (char*) current; +} + +int unhexchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; +} + +int unoctchar(char c) { + + if (c >= '0' && c <= '7') + return c - '0'; + + return -1; +} diff --git a/install/util.h b/install/util.h index 3d7c2320..48208427 100644 --- a/install/util.h +++ b/install/util.h @@ -134,8 +134,16 @@ static inline bool isempty(const char *p) { return !p || !p[0]; } + +static inline const char *startswith(const char *s, const char *prefix) { + if (strncmp(s, prefix, strlen(prefix)) == 0) + return s + strlen(prefix); + return NULL; +} + bool endswith(const char *s, const char *postfix); -bool startswith(const char *s, const char *prefix); + + bool startswith_no_case(const char *s, const char *prefix); bool first_word(const char *s, const char *word); @@ -562,4 +570,16 @@ void warn_melody(void); char *strjoin(const char *x, ...) _sentinel_; +#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ + static inline void func##p(type *p) { \ + if (*p) \ + func(*p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +char *split_quoted(const char *c, size_t *l, char **state); +char *cunescape_length(const char *s, size_t length); +int unhexchar(char c) _const_; +int unoctchar(char c) _const_; + #endif