libexplain  1.4.D001
libexplain/buffer/errno/iconv_open.c
Go to the documentation of this file.
00001 /*
00002  * libexplain - Explain errno values returned by libc functions
00003  * Copyright (C) 2013 Peter Miller
00004  *
00005  * This program is free software; you can redistribute it and/or modify it
00006  * under the terms of the GNU Lesser General Public License as published by
00007  * the Free Software Foundation; either version 3 of the License, or (at
00008  * your option) any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful, but
00011  * WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
00013  * General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU Lesser General Public License
00016  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include <libexplain/ac/assert.h>
00020 #include <libexplain/ac/ctype.h>
00021 #include <libexplain/ac/errno.h>
00022 #include <libexplain/ac/stdio.h>
00023 #include <libexplain/ac/stdlib.h>
00024 #include <libexplain/ac/string.h>
00025 
00026 #include <libexplain/buffer/einval.h>
00027 #include <libexplain/buffer/errno/generic.h>
00028 #include <libexplain/buffer/errno/iconv_open.h>
00029 #include <libexplain/buffer/pathname.h>
00030 #include <libexplain/buffer/is_the_null_pointer.h>
00031 #include <libexplain/explanation.h>
00032 #include <libexplain/fstrcmp.h>
00033 
00034 
00035 static void
00036 explain_buffer_errno_iconv_open_system_call(explain_string_buffer_t *sb, int
00037     errnum, const char *tocode, const char *fromcode)
00038 {
00039     (void)errnum;
00040     explain_string_buffer_puts(sb, "iconv_open(tocode = ");
00041     explain_buffer_pathname(sb, tocode);
00042     explain_string_buffer_puts(sb, ", fromcode = ");
00043     explain_buffer_pathname(sb, fromcode);
00044     explain_string_buffer_putc(sb, ')');
00045 }
00046 
00047 
00048 static char **known_names;
00049 static size_t known_names_size;
00050 static size_t known_names_allocated;
00051 
00052 
00053 static char *
00054 xstrdup(const char *text)
00055 {
00056     char            *result;
00057     size_t          text_size;
00058 
00059     text_size = strlen(text);
00060     result = malloc(text_size + 1);
00061     if (!result)
00062         return (char *)text;
00063     memcpy(result, text, text_size + 1);
00064     return result;
00065 }
00066 
00067 
00068 static void
00069 one_more_name(char *name)
00070 {
00071     if (known_names_size >= known_names_allocated)
00072     {
00073         char            **new_known_names;
00074         size_t          new_known_names_allocated;
00075         size_t          j;
00076 
00077         new_known_names_allocated =
00078             known_names_allocated ? known_names_allocated * 2 : 19;
00079         while (known_names_size > new_known_names_allocated)
00080             new_known_names_allocated *= 2;
00081         assert(known_names_size < new_known_names_allocated);
00082         new_known_names =
00083             malloc(new_known_names_allocated * sizeof(char *));
00084         for (j = 0; j < known_names_size; ++j)
00085             new_known_names[j] = known_names[j];
00086         if (known_names)
00087             free(known_names);
00088         known_names_allocated = new_known_names_allocated;
00089         known_names = new_known_names;
00090         assert(known_names_size < known_names_allocated);
00091     }
00092     known_names[known_names_size++] = xstrdup(name);
00093 }
00094 
00095 
00096 static void
00097 upcase_insitu(char *name)
00098 {
00099     char            *ip;
00100     char            *op;
00101 
00102     /*
00103      * eglibc::iconv/iconv_open.c
00104      * Normalizes the name, by remove all characters beside alpha-numeric,
00105      * '_', '-', '/', '.', and ':'.
00106      */
00107     ip = name;
00108     op = name;
00109     for(;;)
00110     {
00111         unsigned char c = *ip++;
00112         if (!c)
00113         {
00114             *op = '\0';
00115             return;
00116         }
00117         if (islower(c))
00118             *op++ = toupper(c);
00119         else if (isalnum(c))
00120             *op++ = c;
00121         else if (strchr("_-/.:", c))
00122             *op++ = c;
00123         else
00124         {
00125             /* discard */
00126         }
00127     }
00128 }
00129 
00130 
00131 static char *
00132 strdup_upcase(const char *text)
00133 {
00134     const char      *ip;
00135     char            *op;
00136     char            *result;
00137 
00138     result = malloc(strlen(text) + 1);
00139     if (!result)
00140         return NULL;
00141     ip = text;
00142     op = result;
00143     for (;;)
00144     {
00145         unsigned char c = *ip++;
00146         if (!c)
00147         {
00148             *op = '\0';
00149             return result;
00150         }
00151         if (islower(c))
00152             *op++ = toupper(c);
00153         else if (isalnum(c))
00154             *op++ = c;
00155         else if (strchr("_-/.:", c))
00156             *op++ = c;
00157         else
00158         {
00159             /* discard */
00160         }
00161     }
00162     return result;
00163 }
00164 
00165 
00166 static void
00167 get_list_of_known_names(void)
00168 {
00169     FILE            *fp;
00170 
00171     /*
00172      * The only way to do this is via the iconv() command, which has
00173      * special eglibc support
00174      */
00175     fp = popen("iconv --list", "r");
00176     if (!fp)
00177         return;
00178     for (;;)
00179     {
00180         char            buffer[200];
00181         char            *slash;
00182 
00183         if (!fgets(buffer, sizeof(buffer), fp))
00184             break;
00185         slash = strpbrk(buffer, "/\r\n");
00186         if (slash)
00187             *slash = '\0';
00188         upcase_insitu(buffer);
00189         one_more_name(buffer);
00190     }
00191     pclose(fp);
00192 }
00193 
00194 
00195 static int
00196 is_known_name(const char *name)
00197 {
00198     size_t          j;
00199 
00200     for (j = 0; j < known_names_size; ++j)
00201     {
00202         char *known_name;
00203 
00204         known_name = known_names[j];
00205 #ifdef HAV_STRVERSCMP
00206         if (0 == strverscmp(known_name, name))
00207             return 1;
00208 #else
00209         if (0 == strcmp(known_name, name))
00210             return 1;
00211 #endif
00212     }
00213     return 0;
00214 }
00215 
00216 
00217 static char *
00218 known_names_fuzzy(const char *name)
00219 {
00220     char            *best_name;
00221     double          best_weight;
00222     size_t          j;
00223     char            *name2;
00224 
00225     name2 = strdup_upcase(name);
00226     if (!name2)
00227         return NULL;
00228     get_list_of_known_names();
00229     best_name = NULL;
00230     best_weight = 0.6;
00231     for (j = 0; j < known_names_size; ++j)
00232     {
00233         char            *known_name;
00234         double          w;
00235 
00236         known_name = known_names[j];
00237         w = explain_fstrcmp(name2, known_name);
00238         if (w > best_weight)
00239         {
00240             best_name = known_name;
00241             best_weight = w;
00242         }
00243     }
00244     free(name2);
00245     return best_name;
00246 }
00247 
00248 
00249 static int
00250 known_names_check(explain_string_buffer_t *sb, const char *locale,
00251     const char *locale_caption)
00252 {
00253     char            *locale_fuzzy;
00254 
00255     if (!locale)
00256     {
00257         explain_buffer_is_the_null_pointer(sb, locale_caption);
00258         return 1;
00259     }
00260 
00261     get_list_of_known_names();
00262     if (is_known_name(locale))
00263     {
00264         /* no error here */
00265         return 0;
00266     }
00267 
00268     explain_string_buffer_printf
00269     (
00270         sb,
00271         /* FIXME: i18n */
00272         "the %s argument is not a known locale name",
00273         locale_caption
00274     );
00275 
00276     locale_fuzzy = known_names_fuzzy(locale);
00277     if (locale_fuzzy)
00278     {
00279         explain_string_buffer_puts(sb->footnotes, "; ");
00280         explain_string_buffer_printf
00281         (
00282             sb->footnotes,
00283             /* FIXME: i18n */
00284             "did you mean the \"%s\" locale instead?",
00285             locale_fuzzy
00286         );
00287     }
00288     return 1;
00289 }
00290 
00291 
00292 void
00293 explain_buffer_errno_iconv_open_explanation(explain_string_buffer_t *sb, int
00294     errnum, const char *syscall_name, const char *tocode, const char *fromcode)
00295 {
00296     (void)tocode;
00297     (void)fromcode;
00298     switch (errnum)
00299     {
00300     case EINVAL:
00301         if (known_names_check(sb, tocode, "tocode"))
00302             break;
00303         if (known_names_check(sb, fromcode, "fromcode"))
00304             break;
00305 
00306         explain_string_buffer_puts
00307         (
00308             sb,
00309             /* FIXME: i18n */
00310             "The conversion from fromcode to tocode is not supported by "
00311             "the implementation"
00312         );
00313         break;
00314 
00315     default:
00316         explain_buffer_errno_generic(sb, errnum, syscall_name);
00317         break;
00318     }
00319 }
00320 
00321 
00322 void
00323 explain_buffer_errno_iconv_open(explain_string_buffer_t *sb, int errnum, const
00324     char *tocode, const char *fromcode)
00325 {
00326     explain_explanation_t exp;
00327 
00328     explain_explanation_init(&exp, errnum);
00329     explain_buffer_errno_iconv_open_system_call(&exp.system_call_sb, errnum,
00330         tocode, fromcode);
00331     explain_buffer_errno_iconv_open_explanation(&exp.explanation_sb, errnum,
00332         "iconv_open", tocode, fromcode);
00333     explain_explanation_assemble(&exp, sb);
00334 }
00335 
00336 
00337 /* vim: set ts=8 sw=4 et : */