libexplain  1.4.D001
libexplain/path_search.c
Go to the documentation of this file.
00001 /*
00002  * libexplain - Explain errno values returned by libc functions
00003  * Copyright (C) 2009, 2010, 2013 Peter Miller
00004  * Written by Peter Miller <pmiller@opensource.org.au>
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU Lesser General Public License as
00008  * published by the Free Software Foundation; either version 3 of the
00009  * License, or (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Lesser General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Lesser General Public License
00017  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00018  *
00019  *
00020  * Loosely based on code from [e]glibc marked
00021  *     Copyright (C) 1991-2001, 2006, 2007 Free Software Foundation, Inc.
00022  *     The GNU C Library is free software; you can redistribute it and/or
00023  *     modify it under the terms of the GNU Lesser General Public
00024  *     License as published by the Free Software Foundation; either
00025  *     version 2.1 of the License, or (at your option) any later version.
00026  */
00027 
00028 #include <libexplain/ac/errno.h>
00029 #include <libexplain/ac/stdio.h>
00030 #include <libexplain/ac/stdlib.h>
00031 #include <libexplain/ac/string.h>
00032 #include <libexplain/ac/sys/stat.h>
00033 
00034 #include <libexplain/path_search.h>
00035 #include <libexplain/program_name.h>
00036 #include <libexplain/buffer/eexist.h>
00037 #include <libexplain/buffer/software_error.h>
00038 
00039 
00040 static int
00041 direxists(const char *pathname)
00042 {
00043     struct stat     st;
00044 
00045     return
00046         (
00047             pathname
00048         &&
00049             pathname[0]
00050         &&
00051             stat(pathname, &st) >= 0
00052         &&
00053             S_ISDIR(st.st_mode)
00054         );
00055 }
00056 
00057 
00058 int
00059 explain_path_search(char *pathname, size_t pathname_size, const char *dir,
00060     const char *prefix, int try_tmpdir)
00061 {
00062     const char      *where;
00063     size_t          where_size;
00064     size_t          prefix_size;
00065 
00066     where = NULL;
00067     if (try_tmpdir)
00068     {
00069         const char      *d;
00070 
00071         /*
00072          * Strangley, [e]glibc tries TMPDIR before the directory in
00073          * out argument list.  But the comments in the [e]glibc code
00074          * indicate that it is the other way around.  Huh?!?
00075          */
00076         d = getenv("TMPDIR");
00077         if (direxists(d))
00078             where = d;
00079     }
00080     if (where == NULL && direxists(dir))
00081         where = dir;
00082     if (where == NULL && direxists(P_tmpdir))
00083         where = P_tmpdir;
00084     if (where == NULL)
00085     {
00086         const char      *d;
00087 
00088         d = "/tmp";
00089         if (direxists(d))
00090             where = d;
00091     }
00092     if (where == NULL)
00093     {
00094         errno = ENOENT;
00095         return -1;
00096     }
00097 
00098     where_size = strlen(where);
00099     /* remove trailing slashes */
00100     while (where_size > 1 && where[where_size - 1] == '/')
00101         --where_size;
00102 
00103     /*
00104      * Make sure the prefix is sensable.
00105      */
00106     if (!prefix || !prefix[0])
00107         prefix = explain_program_name_get();
00108     if (!prefix || !prefix[0])
00109         prefix = "file";
00110     prefix_size = strlen(prefix);
00111     if (prefix_size > 5)
00112     {
00113         /*
00114          * This is the same logic that [e]glibc does.
00115          *
00116          * Only use up to the first 5 characters of prefix, to ensure
00117          * that file name length is <= 11 characters.  But why?  FAT
00118          * only has 8, V7 Unix had 14, and modern ones have about 255.
00119          * So why 11?
00120          *
00121          * Woudn't it be better to use pathconf(PC_NAMEMAX)?
00122          */
00123         prefix_size = 5;
00124     }
00125 
00126     /*
00127      * Check we have room for "${where}/${prefix}XXXXXX\0"
00128      */
00129     if (pathname_size < where_size + prefix_size + 8)
00130     {
00131         errno = EINVAL;
00132         return -1;
00133     }
00134 
00135     /*
00136      * Build the path.
00137      */
00138     snprintf
00139     (
00140         pathname,
00141         pathname_size,
00142         "%.*s/%.*sXXXXXX",
00143         (int)where_size,
00144         where,
00145         (int)prefix_size,
00146         prefix
00147     );
00148 
00149     /*
00150      * Report success.
00151      */
00152     return 0;
00153 }
00154 
00155 
00156 static void
00157 paths_push(const char **paths, size_t *paths_count, const char *path)
00158 {
00159     size_t          j;
00160 
00161     if (!path)
00162         return;
00163     if (!*path)
00164         return;
00165     /*
00166      * Note: we do not check that the path actually is a directory,
00167      * this is for the error message, where we are complaining that we
00168      * couldn't find a directory.
00169      */
00170     for (j = 0; j < *paths_count; ++j)
00171         if (strcmp(paths[j], path) == 0)
00172             return;
00173     paths[*paths_count] = path;
00174     ++*paths_count;
00175 }
00176 
00177 
00178 int
00179 explain_path_search_explanation(explain_string_buffer_t *sb, int errnum,
00180     const char *dir, int try_tmpdir)
00181 {
00182     switch (errnum)
00183     {
00184     case ENOENT:
00185         {
00186             const char      *paths[4];
00187             size_t          paths_count;
00188             size_t          j;
00189             char            paths_text[1000];
00190             explain_string_buffer_t paths_text_sb;
00191 
00192             /*
00193              * Build a text string describing the paths searched to
00194              * find a temporary directory.
00195              */
00196             paths_count = 0;
00197             if (try_tmpdir)
00198                 paths_push(paths, &paths_count, getenv("TMPDIR"));
00199             paths_push(paths, &paths_count, dir);
00200             paths_push(paths, &paths_count, P_tmpdir);
00201             paths_push(paths, &paths_count, "/tmp");
00202             explain_string_buffer_init(&paths_text_sb, paths_text,
00203                 sizeof(paths_text_sb));
00204             explain_string_buffer_printf(&paths_text_sb, "%d: ",
00205                 (int)paths_count);
00206             for (j = 0; j < paths_count; ++j)
00207             {
00208                 if (j)
00209                     explain_string_buffer_puts(&paths_text_sb, ", ");
00210                 explain_string_buffer_puts_quoted(&paths_text_sb, paths[j]);
00211             }
00212 
00213             explain_string_buffer_printf_gettext
00214             (
00215                 sb,
00216                 /*
00217                  * xgettext:  This error message is issued when we
00218                  * are unable to locate a temporary directory in
00219                  * which to create temporary files (ENOENT).
00220                  *
00221                  * %1$s => The list of directories tried, already quoted.
00222                  */
00223                 i18n("the system was unable to find a temporary directory, "
00224                     "tried %s"),
00225                 paths_text
00226             );
00227         };
00228         return 0;
00229 
00230     case EINVAL:
00231         /* FIXME: i18n */
00232         explain_string_buffer_puts
00233         (
00234             sb,
00235             "the buffer allocated for template file name was too small"
00236         );
00237         explain_buffer_software_error(sb);
00238         return 0;
00239 
00240     case EEXIST:
00241         {
00242             const char      *where;
00243 
00244             where = 0;
00245             if (try_tmpdir)
00246             {
00247                 const char *d;
00248 
00249                 d = getenv("TMPDIR");
00250                 if (direxists(d))
00251                     where = d;
00252             }
00253             if (!where && direxists(dir))
00254                 where = dir;
00255             if (!where && direxists(P_tmpdir))
00256                 where = P_tmpdir;
00257             if (!where)
00258                 where = "/tmp";
00259 
00260             explain_buffer_eexist_tempname(sb, where);
00261         }
00262         return 0;
00263 
00264     default:
00265         return -1;
00266     }
00267 }
00268 
00269 
00270 /* vim: set ts=8 sw=4 et : */