libexplain
1.4.D001
|
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 : */