1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2020 Joyent, Inc.
14 */
15
16 #include <ctype.h>
17 #include <demangle-sys.h>
18 #include <err.h>
19 #include <errno.h>
20 #include <libcustr.h>
21 #include <locale.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #define _(x) gettext(x)
26
27 locale_t c_locale;
28
29 static int do_symbols(sysdem_lang_t, int, char * const *);
30 static int do_input(sysdem_lang_t, FILE *restrict, FILE *restrict);
31 static int do_demangle(const char *, sysdem_lang_t, FILE *);
32 static void appendc(custr_t *, char);
33 static void xputc(int, FILE *);
34
35 static void
usage(void)36 usage(void)
37 {
38 (void) fprintf(stderr, _("Usage: %s [-l lang] [sym...]\n"),
39 getprogname());
40 exit(2);
41 }
42
43 int
main(int argc,char * const * argv)44 main(int argc, char * const *argv)
45 {
46 sysdem_lang_t lang = SYSDEM_LANG_AUTO;
47 int c;
48 int ret;
49
50 (void) setlocale(LC_ALL, "");
51
52 #if !defined(TEXT_DOMAIN)
53 #define TEXT_DOMAIN "SYS_TEST"
54 #endif
55 (void) textdomain(TEXT_DOMAIN);
56
57 /*
58 * For detecting symbol boundaries, we want to use the C locale
59 * definitions for use in isalnum_l().
60 */
61 if ((c_locale = newlocale(LC_CTYPE_MASK, "C", NULL)) == NULL)
62 err(EXIT_FAILURE, _("failed to construct C locale"));
63
64 while ((c = getopt(argc, argv, "hl:")) != -1) {
65 switch (c) {
66 case 'l':
67 if (sysdem_parse_lang(optarg, &lang))
68 break;
69
70 errx(EXIT_FAILURE, _("Unsupported language '%s'\n"),
71 optarg);
72 case 'h':
73 case '?':
74 usage();
75 }
76 }
77
78 argc -= optind;
79 argv += optind;
80
81 if (argc > 0)
82 ret = do_symbols(lang, argc, argv);
83 else
84 ret = do_input(lang, stdin, stdout);
85
86 return ((ret < 0) ? EXIT_FAILURE : EXIT_SUCCESS);
87 }
88
89 static int
do_symbols(sysdem_lang_t lang,int argc,char * const * argv)90 do_symbols(sysdem_lang_t lang, int argc, char * const *argv)
91 {
92 int ret = 0;
93
94 for (int i = 0; i < argc; i++) {
95 if (do_demangle(argv[i], lang, stdout) < 0)
96 ret = -1;
97 else
98 xputc('\n', stdout);
99 }
100
101 return (ret);
102 }
103
104 static int
do_input(sysdem_lang_t lang,FILE * restrict in,FILE * restrict out)105 do_input(sysdem_lang_t lang, FILE *restrict in, FILE *restrict out)
106 {
107 custr_t *word = NULL;
108 int c;
109 int ret = 0;
110 boolean_t in_symbol = B_FALSE;
111
112 if (custr_alloc(&word) != 0)
113 err(EXIT_FAILURE, _("failed to allocate memory"));
114
115 while ((c = fgetc(in)) != EOF) {
116 if (in_symbol) {
117 /*
118 * All currently supported mangling formats only use
119 * alphanumeric characters, '.', '_', or '$' in
120 * mangled names. Once we've seen the potential start
121 * of a symbol ('_'), we accumulate subsequent
122 * charaters into 'word'. If we encounter a character
123 * that is not a part of that set ([A-Za-z0-9._$]), we
124 * treat it as a delimiter, we stop accumulating
125 * characters into word, and we attempt to demangle the
126 * accumulated string in 'word' by calling
127 * demangle_custr().
128 *
129 * Similar utilities like c++filt behave in a similar
130 * fashion when reading from stdin to allow for
131 * demangling of symbols embedded in surrounding text.
132 */
133 if (isalnum_l(c, c_locale) || c == '.' || c == '_' ||
134 c == '$') {
135 appendc(word, c);
136 continue;
137 }
138
139 /*
140 * Hit a symbol boundary, attempt to demangle what
141 * we've accumulated in word and reset word.
142 */
143 if (do_demangle(custr_cstr(word), lang, out) < 0)
144 ret = -1;
145
146 custr_reset(word);
147 in_symbol = B_FALSE;
148 }
149
150 if (c != '_') {
151 xputc(c, out);
152 } else {
153 in_symbol = B_TRUE;
154 appendc(word, c);
155 }
156 }
157
158 if (ferror(in))
159 err(EXIT_FAILURE, _("error reading input"));
160
161 /*
162 * If we were accumulating characters for a symbol and hit EOF,
163 * attempt to demangle what we accumulated.
164 */
165 if (custr_len(word) > 0 && do_demangle(custr_cstr(word), lang, out) < 0)
166 ret = -1;
167
168 custr_free(word);
169 return (ret);
170 }
171
172 /*
173 * Attempt to demangle 'sym' as a symbol for 'lang' and write the result
174 * to 'out'. If 'sym' could not be demangled as 'lang' symbol, the original
175 * string is output instead.
176 *
177 * If an error other than 'not a mangled symbol' is encountered (e.g. ENOMEM),
178 * a warning is sent to stderr and -1 is returned. Otherwise, 0 is returned
179 * (including when 'sym' is merely not a mangled symbol of 'lang').
180 */
181 static int
do_demangle(const char * sym,sysdem_lang_t lang,FILE * out)182 do_demangle(const char *sym, sysdem_lang_t lang, FILE *out)
183 {
184 char *demangled = sysdemangle(sym, lang, NULL);
185
186 if (demangled == NULL && errno != EINVAL && errno != ENOTSUP) {
187 warn(_("error while demangling '%s'"), sym);
188 return (-1);
189 }
190
191 if (fprintf(out, "%s", (demangled != NULL) ? demangled : sym) < 0)
192 err(EXIT_FAILURE, _("failed to write to output"));
193
194 free(demangled);
195 return (0);
196 }
197
198 static void
appendc(custr_t * cus,char c)199 appendc(custr_t *cus, char c)
200 {
201 if (custr_appendc(cus, c) == 0)
202 return;
203 err(EXIT_FAILURE, _("failed to save character from input"));
204 }
205
206 static void
xputc(int c,FILE * out)207 xputc(int c, FILE *out)
208 {
209 if (fputc(c, out) < 0)
210 err(EXIT_FAILURE, _("failed to write output"));
211 }
212