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 void do_symbols(sysdem_lang_t, int, char * const *);
  30 static void do_input(sysdem_lang_t, FILE *restrict, FILE *restrict);
  31 static void demangle_custr(custr_t *, sysdem_lang_t, FILE *);
  32 static void appendc(custr_t *, char);
  33 
  34 static void
  35 usage(void)
  36 {
  37         (void) fprintf(stderr, _("Usage: %s [-l lang] [sym...]\n"),
  38             getprogname());
  39         exit(2);
  40 }
  41 
  42 int
  43 main(int argc, char * const *argv)
  44 {
  45         sysdem_lang_t lang = SYSDEM_LANG_AUTO;
  46         int c;
  47 
  48         (void) setlocale(LC_ALL, "");
  49 
  50 #if !defined(TEXT_DOMAIN)
  51 #define TEXT_DOMAIN "SYS_TEST"
  52 #endif
  53         (void) textdomain(TEXT_DOMAIN);
  54 
  55         /*
  56          * For detecting symbol boundaries, we want to use the C locale
  57          * definitions for use in isalnum_l().
  58          */
  59         if ((c_locale = newlocale(LC_CTYPE_MASK, "C", NULL)) == NULL)
  60                 err(EXIT_FAILURE, _("failed to construct C locale"));
  61 
  62         while ((c = getopt(argc, argv, "hl:")) != -1) {
  63                 switch (c) {
  64                 case 'l':
  65                         if (sysdem_parse_lang(optarg, &lang))
  66                                 break;
  67 
  68                         errx(EXIT_FAILURE, _("Unsupported language '%s'\n"),
  69                             optarg);
  70                 case 'h':
  71                 case '?':
  72                         usage();
  73                 }
  74         }
  75 
  76         argc -= optind;
  77         argv += optind;
  78 
  79         if (argc > 0)
  80                 do_symbols(lang, argc, argv);
  81         else
  82                 do_input(lang, stdin, stdout);
  83 
  84         return (0);
  85 }
  86 
  87 static void
  88 do_symbols(sysdem_lang_t lang, int argc, char * const *argv)
  89 {
  90         for (int i = 0; i < argc; i++) {
  91                 char *demangled = NULL;
  92 
  93                 demangled = sysdemangle(argv[i], lang, NULL);
  94 
  95                 if (demangled == NULL) {
  96                         /*
  97                          * If we failed to demangle for any reason other than
  98                          * 'not a mangled name' (EINVAL), we print the error.
  99                          * For EINVAL, we just print the original value like
 100                          * c++filt does.
 101                          */
 102                         if (errno != EINVAL)
 103                                 warnx(_("failed to demangle '%s'"), argv[i]);
 104                         else
 105                                 (void) printf("%s\n", argv[i]);
 106                 } else {
 107                         (void) printf("%s\n", demangled);
 108                         free(demangled);
 109                 }
 110         }
 111 }
 112 
 113 static void
 114 do_input(sysdem_lang_t lang, FILE *restrict in, FILE *restrict out)
 115 {
 116         custr_t *word = NULL;
 117         int c;
 118         boolean_t in_symbol = B_FALSE;
 119 
 120         if (custr_alloc(&word) != 0)
 121                 err(EXIT_FAILURE, _("failed to allocate memory"));
 122 
 123         while ((c = fgetc(in)) != EOF) {
 124                 if (in_symbol) {
 125                         /*
 126                          * All currently supported mangling formats only use
 127                          * alphanumeric characters, '.', '_', or '$' in
 128                          * mangled names. Once we've seen the potential start
 129                          * of a symbol ('_'), we accumulate subsequent
 130                          * charaters into 'word'. If we encounter a character
 131                          * that is not a part of that set ([A-Za-z0-9._$]), we
 132                          * treat it as a delimiter, we stop accumulating
 133                          * characters into word, and we attempt to demangle the
 134                          * accumulated string in 'word' by calling
 135                          * demangle_custr().
 136                          *
 137                          * Similar utilities like c++filt behave in a similar
 138                          * fashion when reading from stdin to allow for
 139                          * demangling of symbols embedded in surrounding text.
 140                          */
 141                         if (isalnum_l(c, c_locale) || c == '.' || c == '_' ||
 142                             c == '$') {
 143                                 appendc(word, c);
 144                                 continue;
 145                         }
 146 
 147                         /*
 148                          * Hit a symbol boundary, attempt to demangle what
 149                          * we've accumulated in word and reset word.
 150                          */
 151                         demangle_custr(word, lang, out);
 152                         in_symbol = B_FALSE;
 153                 }
 154 
 155                 if (c != '_') {
 156                         if (fputc(c, out) != c) {
 157                                 err(EXIT_FAILURE,
 158                                     _("failed to write to output"));
 159                         }
 160                 } else {
 161                         in_symbol = B_TRUE;
 162                         appendc(word, c);
 163                 }
 164         }
 165 
 166         if (ferror(in))
 167                 err(EXIT_FAILURE, _("error reading input"));
 168 
 169         /*
 170          * If we were accumulating characters for a symbol and hit EOF,
 171          * attempt to demangle what we accumulated.
 172          */
 173         if (custr_len(word) > 0)
 174                 demangle_custr(word, lang, out);
 175 
 176         custr_free(word);
 177 }
 178 
 179 /*
 180  * Attempt to demangle the string in 'word' and write to out, otherwise
 181  * write out the contents of word. In either case, 'word' is reset (contents
 182  * cleared) prior to returning.
 183  */
 184 static void
 185 demangle_custr(custr_t *word, sysdem_lang_t lang, FILE *out)
 186 {
 187         char *demangled = NULL;
 188 
 189         demangled = sysdemangle(custr_cstr(word), lang, NULL);
 190 
 191         if (fprintf(out, "%s",
 192             (demangled != NULL) ? demangled : custr_cstr(word)) < 0) {
 193                 err(EXIT_FAILURE, _("failed to write to output"));
 194         }
 195 
 196         free(demangled);
 197         custr_reset(word);
 198 }
 199 
 200 static void
 201 appendc(custr_t *cus, char c)
 202 {
 203         if (custr_appendc(cus, c) == 0)
 204                 return;
 205         err(EXIT_FAILURE, _("failed to save character from input"));
 206 }