libexplain  1.4.D001
libexplain/buffer/errno/execvp.c
Go to the documentation of this file.
00001 /*
00002  * libexplain - Explain errno values returned by libc functions
00003  * Copyright (C) 2009-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,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  * Lesser 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/errno.h>
00020 #include <libexplain/ac/stdlib.h>
00021 #include <libexplain/ac/string.h>
00022 #include <libexplain/ac/sys/stat.h>
00023 #include <libexplain/ac/unistd.h>
00024 
00025 #include <libexplain/buffer/caption_name_type.h>
00026 #include <libexplain/buffer/efault.h>
00027 #include <libexplain/buffer/errno/execve.h>
00028 #include <libexplain/buffer/errno/execvp.h>
00029 #include <libexplain/buffer/pathname.h>
00030 #include <libexplain/buffer/pointer.h>
00031 #include <libexplain/explanation.h>
00032 #include <libexplain/have_permission.h>
00033 #include <libexplain/name_max.h>
00034 #include <libexplain/is_efault.h>
00035 #include <libexplain/sizeof.h>
00036 
00037 
00038 static size_t
00039 count(char *const *p)
00040 {
00041     size_t          result;
00042 
00043     result = 0;
00044     while (*p)
00045     {
00046         ++result;
00047         ++p;
00048     }
00049     return result;
00050 }
00051 
00052 
00053 static void
00054 explain_buffer_errno_execvp_system_call(explain_string_buffer_t *sb,
00055     int errnum, const char *pathname, char *const *argv)
00056 {
00057     (void)errnum;
00058     explain_string_buffer_puts(sb, "execvp(pathname = ");
00059     explain_buffer_pathname(sb, pathname);
00060     explain_string_buffer_puts(sb, ", argv = ");
00061     if (explain_is_efault_pointer(argv, sizeof(argv[0])))
00062         explain_buffer_pointer(sb, argv);
00063     else
00064     {
00065         size_t          argc;
00066         size_t          n;
00067         size_t          argsize;
00068 
00069         /*
00070          * produce output similar to execve
00071          */
00072         argc = count(argv);
00073         explain_string_buffer_putc(sb, '[');
00074         argsize = 0;
00075         for (n = 0; n < argc && argsize < 1000; ++n)
00076         {
00077             const char      *s;
00078 
00079             if (n)
00080                 explain_string_buffer_puts(sb, ", ");
00081             s = argv[n];
00082             if (explain_is_efault_path(s))
00083             {
00084                 explain_buffer_pointer(sb, s);
00085                 argsize += 8;
00086             }
00087             else
00088             {
00089                 explain_string_buffer_puts_quoted(sb, s);
00090                 argsize += strlen(s);
00091             }
00092         }
00093         if (n < argc)
00094         {
00095             explain_string_buffer_printf
00096             (
00097                 sb,
00098                 /* FIXME: i18n */
00099                 "... plus another %d command line arguments",
00100                 (int)(count(argv) - n)
00101             );
00102         }
00103         explain_string_buffer_putc(sb, ']');
00104     }
00105     explain_string_buffer_putc(sb, ')');
00106 }
00107 
00108 
00109 static int
00110 can_execute(const char *pathname)
00111 {
00112     struct stat     st;
00113     explain_have_identity_t hid;
00114 
00115     if (stat(pathname, &st) < 0)
00116         return 0;
00117     explain_have_identity_init(&hid);
00118     errno = EACCES;
00119     return explain_have_execute_permission(&st, &hid);
00120 }
00121 
00122 
00123 void
00124 explain_buffer_errno_execvp_explanation(explain_string_buffer_t *sb,
00125     int errnum, const char *syscall_name, const char *pathname,
00126     char *const *argv)
00127 {
00128     const char      *p;
00129     const char      *path;
00130     char            dpath[100];
00131     size_t          path_len;
00132     size_t          pathname_len;
00133     size_t          full_pathname_len;
00134     char            *full_pathname;
00135     char            *start_of_name;
00136 
00137     if (explain_is_efault_path(pathname))
00138     {
00139         explain_buffer_efault(sb, "pathname");
00140         return;
00141     }
00142 
00143     path = getenv("PATH");
00144     if (!path)
00145     {
00146         if (!confstr(_CS_PATH, dpath, sizeof(dpath)))
00147             explain_strendcpy(dpath, ".:/bin:/usr/bin", ENDOF(dpath));
00148         path = dpath;
00149     }
00150     path_len = strlen(path);
00151 
00152     if (errnum == ENOENT)
00153     {
00154         char qcmd[NAME_MAX];
00155         explain_string_buffer_t qcmd_sb;
00156         char qpath[1000]; /* deliberately short */
00157         explain_string_buffer_t qpath_sb;
00158 
00159         explain_string_buffer_init(&qcmd_sb, qcmd, sizeof(qcmd));
00160         explain_buffer_caption_name_type(&qcmd_sb, 0, pathname, S_IFREG);
00161 
00162         explain_string_buffer_init(&qpath_sb, qpath, sizeof(qpath));
00163 
00164         p = path;
00165         for (;;)
00166         {
00167             const char      *begin;
00168 
00169             begin = p;
00170             p = strchr(begin, ':');
00171             if (!p)
00172                 p = path + strlen(path);
00173 
00174             if (qpath_sb.position != 0)
00175                 explain_string_buffer_puts(&qpath_sb, ", ");
00176             if (p == begin)
00177             {
00178                 explain_string_buffer_puts_quoted(&qpath_sb, ".");
00179             }
00180             else
00181             {
00182                 size_t          len;
00183 
00184                 len = p - begin;
00185                 if
00186                 (
00187                     qpath_sb.position != 0
00188                 &&
00189                     qpath_sb.position + len + 5 > qpath_sb.maximum
00190                 )
00191                 {
00192                     explain_string_buffer_puts(&qpath_sb, "...");
00193                     break;
00194                 }
00195                 explain_string_buffer_puts_quoted_n(&qpath_sb, begin, len);
00196             }
00197 
00198             if (*p == '\0')
00199                 break;
00200             ++p;
00201         }
00202 
00203         explain_string_buffer_printf
00204         (
00205             sb,
00206             /*
00207              * xgettext: This message is used to explain an ENOENT error
00208              * returned by the execvp(3) system call.
00209              *
00210              * %1$s => the name and file type of the command, already quoted.
00211              *         e.g. "\"bogus\" regular file"
00212              * %2$s => the command search PATH, already quoted.
00213              */
00214             i18n("there is no %s in any of the command search PATH "
00215                 "directories (%s)"),
00216             qcmd,
00217             qpath
00218         );
00219         return;
00220     }
00221 
00222     if (!*pathname || strchr(pathname, '/'))
00223     {
00224         goto give_default_explanation;
00225     }
00226 
00227     /*
00228      * Now we simulate execvp, based on the logic in the code from
00229      * glibc, and heavily modified.
00230      */
00231     pathname_len = strlen(pathname);
00232     full_pathname_len = path_len + pathname_len + 2;
00233     full_pathname = malloc(full_pathname_len);
00234     if (!full_pathname)
00235     {
00236         goto give_default_explanation;
00237     }
00238 
00239     /*
00240      * Copy the pathname at the top,
00241      * and add the slash.
00242      */
00243     start_of_name = full_pathname + path_len + 1;
00244     memcpy(start_of_name, pathname, pathname_len + 1);
00245     *--start_of_name = '/';
00246 
00247     p = path;
00248     for (;;)
00249     {
00250         const char      *begp;
00251         char            *command_path;
00252 
00253         begp = p;
00254         p = strchr(begp, ':');
00255         if (!p)
00256             p = begp + strlen(begp);
00257 
00258         if (p == begp)
00259         {
00260             /*
00261              * Two adjacent colons, or a colon at the beginning or the
00262              * end of PATH, means to search the current directory.
00263              */
00264             command_path = start_of_name + 1;
00265         }
00266         else
00267         {
00268             size_t          part_len;
00269 
00270             part_len = p - begp;
00271             command_path = start_of_name - part_len;
00272             memcpy(command_path, begp, part_len);
00273         }
00274 
00275         /*
00276          * The real execvp tries to execute this name..  If it works,
00277          * execve will not return.
00278          *     execve(command_path, argv, environ);
00279          * We are simulating execvp, so if the file is executable, we
00280          * failed to preproduce the problem.
00281          */
00282         if (can_execute(command_path))
00283         {
00284             /* unable to reproduce the problem */
00285             free(full_pathname);
00286             return;
00287         }
00288         if (errno == errnum)
00289         {
00290             found_it:
00291             explain_buffer_errno_execve_explanation
00292             (
00293                 sb,
00294                 errnum,
00295                 syscall_name,
00296                 command_path,
00297                 argv,
00298                 environ
00299             );
00300             free(full_pathname);
00301             return;
00302         }
00303         /*
00304          * Here we are switching on the errno returned by can_execute,
00305          * to see what went wrong with this attempt (NOT errnum).
00306          */
00307         switch (errno)
00308         {
00309         case EACCES:
00310             goto found_it;
00311 
00312         case ENOENT:
00313         case ESTALE:
00314         case ENOTDIR:
00315             /*
00316              * Those errors indicate the file is missing or not
00317              * executable by us, in which case we want to just try the
00318              * next path directory.
00319              */
00320             break;
00321 
00322         case ENODEV:
00323         case ETIMEDOUT:
00324             /*
00325              * Some strange filesystems like AFS return even stranger
00326              * error numbers.  They cannot reasonably mean anything else
00327              * so ignore those, too.
00328              */
00329             break;
00330 
00331         default:
00332             goto found_it;
00333         }
00334         if (*p == '\0')
00335             break;
00336         ++p;
00337     }
00338     free(full_pathname);
00339 
00340     /*
00341      * Nothing happened, give the default explanation.
00342      */
00343     give_default_explanation:
00344     explain_buffer_errno_execve_explanation
00345     (
00346         sb,
00347         errnum,
00348         syscall_name,
00349         pathname,
00350         argv,
00351         environ
00352     );
00353 }
00354 
00355 
00356 void
00357 explain_buffer_errno_execvp(explain_string_buffer_t *sb, int errnum,
00358     const char *pathname, char *const *argv)
00359 {
00360     explain_explanation_t exp;
00361 
00362     explain_explanation_init(&exp, errnum);
00363     explain_buffer_errno_execvp_system_call
00364     (
00365         &exp.system_call_sb,
00366         errnum,
00367         pathname,
00368         argv
00369     );
00370     explain_buffer_errno_execvp_explanation
00371     (
00372         &exp.explanation_sb,
00373         errnum,
00374         "execvp",
00375         pathname,
00376         argv
00377     );
00378     explain_explanation_assemble(&exp, sb);
00379 }
00380 
00381 /* vim: set ts=8 sw=4 et : */